<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Manuel Canga</title>
    <description>The latest articles on DEV Community by Manuel Canga (@manuelcanga).</description>
    <link>https://dev.to/manuelcanga</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F980234%2Fc2e35931-8116-4d3d-9aca-bc1554ee469c.jpeg</url>
      <title>DEV Community: Manuel Canga</title>
      <link>https://dev.to/manuelcanga</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/manuelcanga"/>
    <language>en</language>
    <item>
      <title>Escribe código Python modular y extensible con Hooks al estilo WordPress (Te presento wphooks)</title>
      <dc:creator>Manuel Canga</dc:creator>
      <pubDate>Thu, 11 Dec 2025 17:16:07 +0000</pubDate>
      <link>https://dev.to/manuelcanga/escribe-codigo-python-modular-y-extensible-con-hooks-al-estilo-wordpress-te-presento-wphooks-3ffa</link>
      <guid>https://dev.to/manuelcanga/escribe-codigo-python-modular-y-extensible-con-hooks-al-estilo-wordpress-te-presento-wphooks-3ffa</guid>
      <description>&lt;p&gt;Si alguna vez has trabajado con WordPress como desarrollador, ya conoces su superpoder secreto: &lt;strong&gt;hooks&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
Las acciones (actions) y filtros (filters) son la base de la extensibilidad de WordPress y permiten que los plugins modifiquen casi cualquier cosa sin tocar los archivos principales.&lt;/p&gt;

&lt;p&gt;Ahora imagina traer esa misma extensibilidad a Python — de manera limpia, ligera y agnóstica a frameworks.&lt;/p&gt;

&lt;p&gt;Eso es exactamente lo que hace &lt;strong&gt;wphooks&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PyPI:&lt;/strong&gt; &lt;code&gt;pip install wphooks&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/manuelcanga/wphooks/" rel="noopener noreferrer"&gt;https://github.com/manuelcanga/wphooks/&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  ¿Por qué debería interesarte los Hooks en Python?
&lt;/h2&gt;

&lt;p&gt;Python ya tiene signals (Django), observadores, emisores de eventos e incluso librerías como &lt;code&gt;blinker&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
Son útiles, pero ninguna de ellas recrea completamente la &lt;strong&gt;experiencia de extensibilidad estilo plugin&lt;/strong&gt; que ofrece WordPress:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“Haz algo en este punto específico del flujo de ejecución”
&lt;/li&gt;
&lt;li&gt;“Modifica un valor antes de usarlo”
&lt;/li&gt;
&lt;li&gt;“Añade nueva funcionalidad sin tocar el código principal”
&lt;/li&gt;
&lt;li&gt;“Mantén las nuevas funcionalidades en módulos separados, no dentro de funciones superlargas que nadie quiere tocar.“&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Aquí es donde &lt;strong&gt;wphooks&lt;/strong&gt; destaca.&lt;/p&gt;

&lt;p&gt;Trae los mismos conceptos que impulsan al 40% de la web directamente a Python:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Actions&lt;/strong&gt; → lanza eventos&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filters&lt;/strong&gt; → transformar datos mediante una cadena de funciones
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API mínima&lt;/strong&gt; → &lt;code&gt;add_action()&lt;/code&gt;, &lt;code&gt;do_action()&lt;/code&gt;, &lt;code&gt;add_filter()&lt;/code&gt;, &lt;code&gt;apply_filters()&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sin frameworks. Sin abstracciones pesadas. Todo plug and play.&lt;/p&gt;


&lt;h2&gt;
  
  
  Usa los Hooks en código legacy
&lt;/h2&gt;

&lt;p&gt;Imagina esta situación clásica:&lt;/p&gt;

&lt;p&gt;Heredas una función de 1000 líneas como &lt;code&gt;process_order()&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
Es crítica para el negocio. Nadie se atreve a tocarla.&lt;br&gt;&lt;br&gt;
Ahora los de producto te piden: “Envia notificación a Slack cuando se procese un pedido”.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Opción A:&lt;/strong&gt; editas la función enorme y cruza los dedos para que no se rompa nada.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Opción B:&lt;/strong&gt; añades un hook y mantienes la nueva lógica en un archivo limpio y separado.&lt;/p&gt;
&lt;h3&gt;
  
  
  Con wphooks:
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Dentro de la función legacy:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# lógica existente...
&lt;/span&gt;    &lt;span class="nf"&gt;do_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;order_processed&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;En un archivo/módulo separado:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_slack_notification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;slack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Pedido &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; recibido!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;order_processed&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;send_slack_notification&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Este es el &lt;strong&gt;principio abierto/cerrado (Open/Closed Principle)&lt;/strong&gt; en acción:&lt;br&gt;
Tu función principal sigue cerrada a modificaciones, pero abierta a extensiones.&lt;/p&gt;


&lt;h2&gt;
  
  
  Actions: “Haz algo cuando ocurra esto”
&lt;/h2&gt;

&lt;p&gt;Las actions permiten adjuntar cualquier número de funciones a un evento y ejecutarlas en el momento adecuado.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;wphooks&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;do_action&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_welcome_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Enviando correo de bienvenida a &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;user_registered&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;send_welcome_email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;do_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;user_registered&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Perfecto para:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;enviar emails&lt;/li&gt;
&lt;li&gt;logging&lt;/li&gt;
&lt;li&gt;analíticas&lt;/li&gt;
&lt;li&gt;integraciones con terceros&lt;/li&gt;
&lt;li&gt;tareas en segundo plano&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Filters: “Modifica un valor antes de usarlo”
&lt;/h2&gt;

&lt;p&gt;Los filters son el valor diferencial de wphooks frente a Django Signals o blinker.&lt;br&gt;
Permiten &lt;strong&gt;transformar datos en cadena&lt;/strong&gt;, al estilo WordPress.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;wphooks&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;add_filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;apply_filters&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;uppercase_title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nf"&gt;add_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;format_title&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uppercase_title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;apply_filters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;format_title&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hola mundo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# HOLA MUNDO
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Se usan para:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;formatear datos&lt;/li&gt;
&lt;li&gt;sanitizar entradas&lt;/li&gt;
&lt;li&gt;ajustes de precios&lt;/li&gt;
&lt;li&gt;preprocesar valores&lt;/li&gt;
&lt;li&gt;extensiones de plugins&lt;/li&gt;
&lt;li&gt;sobrescribir configuraciones&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Se pueden añadir múltiples filtros, y la salida de uno se convierte en la entrada del siguiente.&lt;/p&gt;




&lt;h2&gt;
  
  
  Instalación
&lt;/h2&gt;

&lt;p&gt;Instalar desde PyPI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;wphooks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O desde el código fuente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/manuelcanga/wphooks.git
&lt;span class="nb"&gt;cd &lt;/span&gt;wphooks
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Usalo en tu proyecto, importando:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;wphooks&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;do_action&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;wphooks&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;add_filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;apply_filters&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  ¿Quién debería usar wphooks?
&lt;/h2&gt;

&lt;p&gt;Úsalo si quieres:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API extensibles estilo WordPress en Python&lt;/li&gt;
&lt;li&gt;filtros que transformen datos&lt;/li&gt;
&lt;li&gt;arquitectura ligera tipo plugin&lt;/li&gt;
&lt;li&gt;modernizar código legacy de forma segura&lt;/li&gt;
&lt;li&gt;hooks sin frameworks&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Reflexión final
&lt;/h2&gt;

&lt;p&gt;WordPress demostró hace más de una década que un sistema de hooks simple puede impulsar un ecosistema masivo de plugins, themes y extensiones.&lt;br&gt;
&lt;strong&gt;wphooks trae esta arquitectura probada a Python&lt;/strong&gt;, permitiendo crear aplicaciones limpias, modulares y extensibles con un esfuerzo mínimo.&lt;/p&gt;

&lt;p&gt;Si alguna vez deseaste un equivalente directo de WordPress hooks en Python, ahora lo tienes.&lt;/p&gt;

&lt;p&gt;Instálalo y pruébalo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install wphooks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repositorio y documentación:&lt;br&gt;
&lt;a href="https://github.com/manuelcanga/wphooks/" rel="noopener noreferrer"&gt;https://github.com/manuelcanga/wphooks/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>wordpress</category>
      <category>spanish</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Write Modular, Extensible Python Code Using WordPress-Style Hooks (Introducing wphooks)</title>
      <dc:creator>Manuel Canga</dc:creator>
      <pubDate>Thu, 11 Dec 2025 17:04:11 +0000</pubDate>
      <link>https://dev.to/manuelcanga/write-modular-extensible-python-code-using-wordpress-style-hooks-introducing-wphooks-24aa</link>
      <guid>https://dev.to/manuelcanga/write-modular-extensible-python-code-using-wordpress-style-hooks-introducing-wphooks-24aa</guid>
      <description>&lt;p&gt;If you've ever touched WordPress as a developer, you already know its secret superpower: &lt;strong&gt;hooks&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
Actions and filters are the backbone of WordPress extensibility and the reason plugins can modify almost anything without altering core files.&lt;/p&gt;

&lt;p&gt;Now imagine bringing that same extensibility to Python — in a clean, lightweight, framework-agnostic way.&lt;/p&gt;

&lt;p&gt;That’s exactly what &lt;strong&gt;wphooks&lt;/strong&gt; does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PyPI:&lt;/strong&gt; &lt;code&gt;pip install wphooks&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/manuelcanga/wphooks/" rel="noopener noreferrer"&gt;https://github.com/manuelcanga/wphooks/&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Why Should Python Developers Care About Hooks?
&lt;/h2&gt;

&lt;p&gt;Python already has signals (Django), observers, event emitters, and libraries like &lt;code&gt;blinker&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
They’re great — but none of them fully recreate the &lt;strong&gt;plugin-style extensibility&lt;/strong&gt; that WordPress provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“Do something at this specific moment”&lt;/li&gt;
&lt;li&gt;“Modify this value before using it”&lt;/li&gt;
&lt;li&gt;“Add new behavior without touching core code”&lt;/li&gt;
&lt;li&gt;“Make features live in separate modules, not inside giant legacy functions”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where &lt;strong&gt;wphooks&lt;/strong&gt; shines.&lt;/p&gt;

&lt;p&gt;It brings the same concepts that power 40% of the web directly into Python:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Actions&lt;/strong&gt; → fire events&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filters&lt;/strong&gt; → modify data through chained transformations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minimal API&lt;/strong&gt; → &lt;code&gt;add_action()&lt;/code&gt;, &lt;code&gt;do_action()&lt;/code&gt;, &lt;code&gt;add_filter()&lt;/code&gt;, &lt;code&gt;apply_filters()&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No frameworks. No heavy abstractions. Just plug-and-extend.&lt;/p&gt;


&lt;h2&gt;
  
  
  When Hooks Save You From Legacy Hell
&lt;/h2&gt;

&lt;p&gt;Picture this classic scenario:&lt;/p&gt;

&lt;p&gt;You inherit a 1,000-line function, like &lt;code&gt;process_order()&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
It's business-critical. Nobody dares touch it.&lt;br&gt;&lt;br&gt;
Now product wants “Send Slack notification when order completes”.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option A:&lt;/strong&gt; Edit the massive function and pray nothing breaks.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Option B:&lt;/strong&gt; Add one hook call and keep new logic in a clean new file.&lt;/p&gt;
&lt;h3&gt;
  
  
  With wphooks:
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Inside the legacy function:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# existing logic...
&lt;/span&gt;    &lt;span class="nf"&gt;do_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;order_processed&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;In a separate file/module:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_slack_notification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;slack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Order &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; received!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;order_processed&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;send_slack_notification&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the &lt;strong&gt;Open/Closed Principle&lt;/strong&gt; in practice:&lt;br&gt;
Your legacy function stays closed for modification but open for extension.&lt;/p&gt;


&lt;h2&gt;
  
  
  Actions: “Do Something When This Happens”
&lt;/h2&gt;

&lt;p&gt;Actions let you attach any number of functions to a named event and run them at the right moment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;wphooks&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;do_action&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_welcome_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Sending welcome email to &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;user_registered&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;send_welcome_email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;do_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;user_registered&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Perfect for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sending emails&lt;/li&gt;
&lt;li&gt;logging&lt;/li&gt;
&lt;li&gt;analytics&lt;/li&gt;
&lt;li&gt;third-party integrations&lt;/li&gt;
&lt;li&gt;background tasks&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Filters: “Modify a Value Before Using It”
&lt;/h2&gt;

&lt;p&gt;Filters are the real differentiator of wphooks compared to Django Signals or blinker.&lt;br&gt;
They allow &lt;strong&gt;chained transformation of data&lt;/strong&gt;, just like WordPress.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;wphooks&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;add_filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;apply_filters&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;uppercase_title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nf"&gt;add_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;format_title&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uppercase_title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;apply_filters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;format_title&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hello world&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# HELLO WORLD
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use filters for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;data formatting&lt;/li&gt;
&lt;li&gt;sanitization&lt;/li&gt;
&lt;li&gt;price adjustments&lt;/li&gt;
&lt;li&gt;pre-processing values&lt;/li&gt;
&lt;li&gt;plugin overrides&lt;/li&gt;
&lt;li&gt;configuration mutation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Multiple filters can modify the value in sequence.&lt;/p&gt;




&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;Install from PyPI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;wphooks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or install from source:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/manuelcanga/wphooks.git
&lt;span class="nb"&gt;cd &lt;/span&gt;wphooks
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Import:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;wphooks&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;do_action&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;wphooks&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;add_filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;apply_filters&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  How wphooks Fits into the Python Landscape
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Compared to Django Signals
&lt;/h3&gt;

&lt;p&gt;Signals are powerful, but:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;they don’t provide filters&lt;/li&gt;
&lt;li&gt;they're tied to Django’s app infrastructure&lt;/li&gt;
&lt;li&gt;they're less suitable for plugin systems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;wphooks is framework-agnostic and intentionally minimal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Compared to blinker
&lt;/h3&gt;

&lt;p&gt;Blinker is an event emitter, not an extensibility layer.&lt;br&gt;
It handles broadcasting well, but doesn't model the “WordPress-style hook pipeline”.&lt;/p&gt;
&lt;h3&gt;
  
  
  Compared to custom observer patterns
&lt;/h3&gt;

&lt;p&gt;You can build your own, sure — but wphooks gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a predictable API&lt;/li&gt;
&lt;li&gt;simple naming conventions&lt;/li&gt;
&lt;li&gt;an instantly recognizable mental model&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Who Should Use wphooks?
&lt;/h2&gt;

&lt;p&gt;Use wphooks if you want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WordPress-style extensibility in Python&lt;/li&gt;
&lt;li&gt;real data-modifying filters&lt;/li&gt;
&lt;li&gt;a lightweight plugin architecture&lt;/li&gt;
&lt;li&gt;a safe way to modernize legacy code&lt;/li&gt;
&lt;li&gt;extensibility without frameworks&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;WordPress proved long ago that a simple hook system can power a massive ecosystem of plugins, themes, and extensions.&lt;br&gt;
&lt;strong&gt;wphooks brings this tried-and-true architecture to Python&lt;/strong&gt;, enabling developers to build clean, modular, and extensible applications with almost zero overhead.&lt;/p&gt;

&lt;p&gt;If you’ve ever wished Python had a direct equivalent to WordPress hooks, now it does.&lt;/p&gt;

&lt;p&gt;Install and try it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install wphooks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repository and documentation:&lt;br&gt;
&lt;a href="https://github.com/manuelcanga/wphooks/" rel="noopener noreferrer"&gt;https://github.com/manuelcanga/wphooks/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>wordpress</category>
      <category>legacy</category>
      <category>beginners</category>
    </item>
    <item>
      <title>How to View Your WordPress Fields in a Different Way</title>
      <dc:creator>Manuel Canga</dc:creator>
      <pubDate>Thu, 05 Dec 2024 06:43:05 +0000</pubDate>
      <link>https://dev.to/manuelcanga/how-to-view-your-wordpress-fields-in-a-different-way-4efj</link>
      <guid>https://dev.to/manuelcanga/how-to-view-your-wordpress-fields-in-a-different-way-4efj</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk2ddzi02mje4t76qesvv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk2ddzi02mje4t76qesvv.png" alt="Your WordPress Fields Differently" width="800" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When working on a WordPress project—whether developing a plugin, creating a custom theme, or managing a live site—understanding metadata is essential. Metadata represents that invisible layer storing key information about your posts, terms, users, and comments. However, accessing these fields can be challenging without the right tools.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Why is Understanding Metadata Important?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Every element in WordPress—a post, user, or taxonomy term—has additional data stored in the database. This metadata can be custom or generated automatically by plugins and themes. Reviewing this information often requires complex SQL queries or detailed database inspections. Having a tool, like &lt;a href="https://wordpress.org/plugins/display-metadata/" rel="noopener noreferrer"&gt;display-metadata&lt;/a&gt;, that allows you to visualize metadata directly from the WordPress admin panel significantly simplifies this task.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Simplifying Metadata Management&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Having an organized and clear way to review metadata offers several advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Easier Interpretation:&lt;/strong&gt; Presenting metadata in a deserialized and structured format makes it easier to understand, even when it contains complex data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Access from the Admin Panel:&lt;/strong&gt; Avoid the need to access the database directly or write custom queries. Everything is available within WordPress's familiar environment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security:&lt;/strong&gt; View all the data stored by your installed plugins.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7cpuee2vn5y1ho7fwhk6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7cpuee2vn5y1ho7fwhk6.png" alt="View Plugin Data" width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Who Can Benefit from This Approach?&lt;/strong&gt;
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;WordPress Developers:&lt;/strong&gt; Quickly accessing metadata is useful for debugging and optimizing plugins or themes. It helps verify that custom fields are managed correctly and detect potential conflicts with other extensions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;System Administrators:&lt;/strong&gt; Maintaining an organized and efficient database is crucial for site performance. Identifying and reviewing metadata makes cleaning unnecessary fields easier and optimizes storage.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Plugin Authors:&lt;/strong&gt; Ensuring that a plugin handles metadata correctly is vital for functionality and compatibility. Reviewing this information streamlines the development and quality control processes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Theme Creators:&lt;/strong&gt; Knowing the available metadata allows you to use it effectively in templates. This information facilitates theme customization, offering greater design flexibility.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Installing the Plugin in WordPress&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Installing a tool to visualize metadata directly from the admin panel is straightforward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Log in to your WordPress admin panel (/wp-admin/).
&lt;/li&gt;
&lt;li&gt;Go to the &lt;strong&gt;Plugins&lt;/strong&gt; section.
&lt;/li&gt;
&lt;li&gt;Select the &lt;strong&gt;Add New&lt;/strong&gt; submenu option.
&lt;/li&gt;
&lt;li&gt;In the search bar, enter &lt;code&gt;trasweb&lt;/code&gt;.
&lt;/li&gt;
&lt;li&gt;Install &lt;a href="https://wordpress.org/plugins/display-metadata/" rel="noopener noreferrer"&gt;display-metadata&lt;/a&gt; and activate it from the search results.
&lt;/li&gt;
&lt;li&gt;Once activated, you will have access to metadata on the edit pages for posts, terms, users, and comments.
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;In Conclusion&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Gaining a clear view of WordPress metadata can transform how you manage and develop your projects. It simplifies site debugging, optimization, and customization, providing deeper insights into stored data. There's no need to guess which fields are active or manually explore the database: the right tool gives you all the information you need, directly in your admin panel.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq3dn5o8rlftgtjj94038.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq3dn5o8rlftgtjj94038.png" alt="View Complex Data Easily" width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>php</category>
      <category>acf</category>
      <category>customfields</category>
    </item>
    <item>
      <title>Cómo ver los campos de tu WordPress de un modo diferente</title>
      <dc:creator>Manuel Canga</dc:creator>
      <pubDate>Thu, 05 Dec 2024 06:40:41 +0000</pubDate>
      <link>https://dev.to/manuelcanga/como-ver-los-campos-de-tu-wordpress-de-un-modo-diferente-97d</link>
      <guid>https://dev.to/manuelcanga/como-ver-los-campos-de-tu-wordpress-de-un-modo-diferente-97d</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk2ddzi02mje4t76qesvv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk2ddzi02mje4t76qesvv.png" alt="Los campos de tu WordPress de un modo diferente" width="800" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cuando trabajas en un proyecto de WordPress, ya sea desarrollando un plugin, creando un tema personalizado o gestionando un sitio en producción, entender los metadatos es esencial. Los metadatos representan esa capa invisible que almacena información clave sobre tus publicaciones, términos, usuarios y comentarios. Sin embargo, acceder a estos campos puede resultar complicado si no cuentas con las herramientas adecuadas.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;¿Por qué es importante conocer los metadatos?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Cada elemento en WordPress —una publicación, un usuario o un término de taxonomía— tiene datos adicionales almacenados en la base de datos. Estos metadatos pueden ser personalizados o generados automáticamente por plugins y temas. Revisar esta información suele implicar consultas SQL complejas o inspecciones detalladas en la base de datos. Disponer de una herramienta, como &lt;a href="https://wordpress.org/plugins/display-metadata/" rel="noopener noreferrer"&gt;Display metadata&lt;/a&gt;,  que permita visualizar los metadatos directamente en el panel de administración facilita considerablemente esta tarea.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Facilitando la gestión de metadatos&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Contar con una forma organizada y clara de revisar los metadatos proporciona múltiples ventajas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Interpretación más sencilla:&lt;/strong&gt; Presentar los metadatos de forma deserializada y estructurada facilita su comprensión, incluso si contienen datos complejos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Acceso desde el panel de administración:&lt;/strong&gt; Evita la necesidad de acceder directamente a la base de datos o de escribir consultas personalizadas. Todo se encuentra disponible en el entorno habitual de WordPress.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Seguridad:&lt;/strong&gt; Visualiza todos los datos que guardan los plugins que tienes instalado.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7cpuee2vn5y1ho7fwhk6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7cpuee2vn5y1ho7fwhk6.png" alt="Visualiza los datos de los plugins" width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;¿A quién puede beneficiar este enfoque?&lt;/strong&gt;
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Desarrolladores de WordPress:&lt;/strong&gt; Acceder rápidamente a los metadatos es útil para depurar y optimizar plugins o temas. Permite verificar que los campos personalizados se gestionan correctamente y detectar posibles conflictos con otras extensiones.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Administradores de sistemas:&lt;/strong&gt; Mantener una base de datos ordenada y eficiente es crucial para el rendimiento del sitio. Poder identificar y revisar los metadatos facilita la limpieza de campos innecesarios y optimiza el almacenamiento.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Autores de plugins:&lt;/strong&gt; Verificar que un plugin maneja correctamente los metadatos ayuda a garantizar su funcionalidad y compatibilidad. Revisar esta información facilita el proceso de desarrollo y control de calidad.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Creadores de temas:&lt;/strong&gt; Conocer los metadatos disponibles permite utilizarlos de manera efectiva en las plantillas. Esta información facilita la personalización del tema, permitiendo una mayor flexibilidad en el diseño.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Instalación del plugin en WordPress&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Instalar una herramienta que permita visualizar los metadatos directamente desde el panel de administración es sencillo:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Inicia sesión en el panel de administración de tu sitio WordPress (/wp-admin/).&lt;/li&gt;
&lt;li&gt;Ve a la sección &lt;strong&gt;Plugins&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Selecciona la opción del submenú &lt;strong&gt;Añadir nuevo&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;En la barra de búsqueda, introduce &lt;code&gt;trasweb&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Instála &lt;a href="https://wordpress.org/plugins/display-metadata/" rel="noopener noreferrer"&gt;display-metadata&lt;/a&gt; y actívalo desde los resultados de búsqueda.&lt;/li&gt;
&lt;li&gt;Una vez activado, tendrás acceso a los metadatos en las páginas de edición de entradas, términos, usuarios y comentarios.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Finalmente&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Contar con una visión clara de los metadatos de WordPress puede transformar la manera en que gestionas y desarrollas tus proyectos. Facilita la depuración, optimización y personalización del sitio, ofreciendo una perspectiva más profunda de los datos almacenados. Ya no es necesario adivinar qué campos están activos o explorar la base de datos manualmente: una herramienta adecuada puede proporcionarte toda la información que necesitas, directamente en tu panel de administración.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq3dn5o8rlftgtjj94038.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq3dn5o8rlftgtjj94038.png" alt="Visualiza datos complejos de manera sencilla" width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>spanish</category>
      <category>wordpress</category>
      <category>php</category>
      <category>acf</category>
    </item>
    <item>
      <title>That Strange PHP Code in Frameworks and CMSs</title>
      <dc:creator>Manuel Canga</dc:creator>
      <pubDate>Sun, 10 Nov 2024 10:35:47 +0000</pubDate>
      <link>https://dev.to/manuelcanga/that-strange-php-code-in-frameworks-and-cmss-351d</link>
      <guid>https://dev.to/manuelcanga/that-strange-php-code-in-frameworks-and-cmss-351d</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F29uo78bdjiemltqv4l7e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F29uo78bdjiemltqv4l7e.png" alt="Desarrollo web" width="512" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; To follow along with this post, it's assumed you have some basic knowledge of programming in PHP.&lt;/p&gt;

&lt;p&gt;This article discusses a PHP code snippet that you’ve likely seen at the top of your favorite CMS or framework. You've probably read that you should always include it at the beginning of every PHP file you develop, for security reasons, although without a very clear explanation of why. I’m referring to this code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;defined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'ABSPATH'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Exit if accessed directly&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This type of code is very common in WordPress files, although it appears in nearly all frameworks and CMSs. In the case of the &lt;strong&gt;Joomla&lt;/strong&gt; CMS, for example, the only change is that instead of &lt;code&gt;ABSPATH&lt;/code&gt;, it uses &lt;code&gt;JEXEC&lt;/code&gt;. Other than that, the logic remains the same. This CMS evolved from a previous system called &lt;strong&gt;Mambo&lt;/strong&gt;, which also used similar code, but with &lt;code&gt;_VALID_MOS&lt;/code&gt; as the constant. If we go even further back, we find that the first CMS to use this kind of code was &lt;strong&gt;PHP-Nuke&lt;/strong&gt; (considered by some to be the first PHP-based CMS).&lt;/p&gt;

&lt;p&gt;The execution flow of &lt;strong&gt;PHP-Nuke&lt;/strong&gt; (and most CMSs and frameworks today) consisted of sequentially loading multiple files to respond to the action the user or visitor had taken on the website. For example, imagine a website from that era hosted at example.net with this CMS installed. Every time the homepage loaded, the system executed a sequence of files in order (this is just an example, not an actual sequence): &lt;code&gt;index.php =&amp;gt; load_modules.php =&amp;gt; modules.php&lt;/code&gt;. In this chain, &lt;code&gt;index.php&lt;/code&gt; was loaded first, which then loaded &lt;code&gt;load_modules.php&lt;/code&gt;, which in turn loaded &lt;code&gt;modules.php&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This execution chain didn’t always start with the first file (&lt;code&gt;index.php&lt;/code&gt;). In fact, anyone could bypass part of the flow by directly accessing one of the other PHP files through its URL (for example, &lt;code&gt;http://example.net/load_modules.php&lt;/code&gt; or &lt;code&gt;http://example.net/modules.php&lt;/code&gt;), which, as we will see, could be risky in many cases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How was this problem solved?&lt;/strong&gt; A security measure was introduced, adding code similar to this at the beginning of each file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;eregi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"modules.php"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$HTTP_SERVER_VARS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'PHP_SELF'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;die&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"You can't access this file directly..."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essentially, this code, placed at the top of a file named &lt;code&gt;modules.php&lt;/code&gt;, checked if &lt;code&gt;modules.php&lt;/code&gt; was being accessed directly via the URL. If so, execution was stopped, displaying the message: “You can’t access this file directly…” If &lt;code&gt;$HTTP_SERVER_VARS['PHP_SELF']&lt;/code&gt; didn’t contain &lt;code&gt;modules.php&lt;/code&gt;, it meant that the normal execution flow was active, allowing the script to continue.&lt;/p&gt;

&lt;p&gt;This code, however, had some limitations. First, the code was different for each file in which it was inserted, which added complexity. Additionally, in certain situations, PHP did not assign a value to &lt;code&gt;$HTTP_SERVER_VARS['PHP_SELF']&lt;/code&gt;, which limited its effectiveness.&lt;/p&gt;

&lt;p&gt;So, what did the developers do? They replaced all those code snippets with a simpler and more efficient version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;defined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'MODULE_FILE'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;die&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"You can't access this file directly..."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this new code, which had become quite popular in the PHP community, the existence of a constant was checked. This constant was defined and assigned a value in the first file of the execution flow (&lt;code&gt;index.php&lt;/code&gt;, &lt;code&gt;home.php&lt;/code&gt;, or a similar file). Therefore, if this constant didn’t exist in any other file in the sequence, it meant that someone had bypassed &lt;code&gt;index.php&lt;/code&gt; and was attempting to access another file directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dangers of Directly Running a PHP File
&lt;/h2&gt;

&lt;p&gt;At this point, you may be thinking that breaking the execution chain must be extremely serious. However, the truth is that, &lt;strong&gt;usually&lt;/strong&gt;, it doesn’t pose a major threat.&lt;/p&gt;

&lt;p&gt;The risk might arise when a PHP error exposes the path to our files. This shouldn’t concern us if the server is configured to suppress errors; even if errors weren’t hidden, the exposed information would be minimal, providing only a few clues to a potential attacker.&lt;/p&gt;

&lt;p&gt;It could also happen that someone accesses files containing HTML fragments (views), revealing part of their content. In most cases, this should not be a cause for concern either.&lt;/p&gt;

&lt;p&gt;Finally, a developer, either by mistake or lack of experience, might place risky code without external dependencies in the middle of an execution flow. This is very uncommon since framework or CMS code generally depends on other classes, functions, or external variables for its execution. So, if an attempt is made to execute a script directly through the URL, errors will arise as these dependencies won’t be found, and the execution won’t proceed.&lt;/p&gt;

&lt;p&gt;So, why add the constant code if there is little reason for concern? The answer is this: "This method also prevents accidental variable injection through a &lt;em&gt;register globals&lt;/em&gt; attack, preventing the PHP file from assuming it's within the application when it’s actually not."&lt;/p&gt;

&lt;h2&gt;
  
  
  Register Globals
&lt;/h2&gt;

&lt;p&gt;Since the early days of PHP, all variables sent via URLs (&lt;code&gt;GET&lt;/code&gt;) or forms (&lt;code&gt;POST&lt;/code&gt;) were automatically converted into global variables. For example, if the file &lt;code&gt;download.php?filepath=/etc/passwd&lt;/code&gt; was accessed, in the &lt;code&gt;download.php&lt;/code&gt; file (and in those depending on it in the execution flow), you could use &lt;code&gt;echo $filepath;&lt;/code&gt; and it would output &lt;code&gt;/etc/passwd&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Inside &lt;code&gt;download.php&lt;/code&gt;, there was no way to know if the variable &lt;code&gt;$filepath&lt;/code&gt; was created by a prior file in the execution chain or if it was tampered with via the URL or &lt;code&gt;POST&lt;/code&gt;. This created significant security vulnerabilities. Let’s look at an example, assuming the &lt;code&gt;download.php&lt;/code&gt; file contains the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;file_exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$filepath&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Content-Description: File Transfer'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Content-Type: application/octet-stream'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Content-Disposition: attachment; filename="'&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$filepath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'"'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Expires: 0'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Cache-Control: must-revalidate'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Pragma: public'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Content-Length: '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;filesize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$filepath&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nb"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Flush system output buffer&lt;/span&gt;
    &lt;span class="nb"&gt;readfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$filepath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The developer likely intended to use a &lt;a href="https://en.wikipedia.org/wiki/Front_controller" rel="noopener noreferrer"&gt;Front Controller&lt;/a&gt; pattern for their code, meaning all web requests would go through a single entry file (&lt;code&gt;index.php&lt;/code&gt;, &lt;code&gt;home.php&lt;/code&gt;, etc.). This file would handle session initialization, load common variables, and finally redirect the request to a specific script (in this case, &lt;code&gt;download.php&lt;/code&gt;) to perform the file download.&lt;/p&gt;

&lt;p&gt;However, an attacker could bypass the intended execution sequence simply by calling &lt;code&gt;download.php?filepath=/etc/passwd&lt;/code&gt;, as mentioned before. PHP would automatically create the global variable &lt;code&gt;$filepath&lt;/code&gt; with the value &lt;code&gt;/etc/passwd&lt;/code&gt;, allowing the attacker to download that file from the system. Serious problem.&lt;/p&gt;

&lt;p&gt;This is only the tip of the iceberg since even more dangerous attacks could be executed with minimal effort. For example, in code like the following, which the programmer might have left as an unfinished script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;require_once&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$base_path&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"/My.class.php"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An attacker could execute any code by using a &lt;a href="https://en.wikipedia.org/wiki/Remote_File_Inclusion" rel="noopener noreferrer"&gt;Remote File Inclusion (RFI)&lt;/a&gt; attack. If the attacker created a file &lt;code&gt;My.class.php&lt;/code&gt; on their own site &lt;code&gt;https://mysite.net&lt;/code&gt; containing any code they wanted to execute, they could call the vulnerable script by passing in their domain: &lt;code&gt;useless_code.php?base_path=https://mysite.net&lt;/code&gt;, and the attack would be complete.&lt;/p&gt;

&lt;p&gt;Another example: in a script named &lt;code&gt;remove_file.inc.php&lt;/code&gt; with the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;file_exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$filename&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nb"&gt;unlink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"File deleted"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;an attacker could call this file directly with a URL like &lt;code&gt;remove_file.inc.php?filename=/etc/hosts&lt;/code&gt;, attempting to delete the &lt;code&gt;/etc/hosts&lt;/code&gt; file from the system (if the system allows it, or other files they have permission to delete).&lt;/p&gt;

&lt;p&gt;In a CMS like WordPress, which also uses global variables internally, these types of attacks were devastating. However, thanks to the constant technique, these and other PHP scripts were protected. Let’s look at the last example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;defined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'ABSPATH'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Exit if accessed directly&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;file_exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$filename&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nb"&gt;unlink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"File deleted"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, if someone attempted to access &lt;code&gt;remove_file.inc.php?filename=/etc/hosts&lt;/code&gt;, the constant would block the access. It is essential that this is a constant because, logically, if it were a variable, an attacker could inject it.&lt;/p&gt;

&lt;p&gt;By now, you may wonder why PHP kept this functionality if it was so dangerous. Also, if you know other scripting languages (JSP, Ruby, etc.), you’ll see they have nothing similar (which is why they also don’t use the constant technique). Recall that PHP was initially created as a C-based templating system, and this behavior made development easier. The good news is that, seeing the issues it caused, PHP maintainers introduced a &lt;code&gt;php.ini&lt;/code&gt; directive called &lt;code&gt;register_globals&lt;/code&gt; (enabled by default) to allow this functionality to be disabled.&lt;/p&gt;

&lt;p&gt;But as problems persisted, they disabled it by default. Even so, many hosts kept enabling it out of fear that their clients’ projects would stop working, as much of the code at the time did not use the recommended &lt;code&gt;HTTP_*_VARS&lt;/code&gt; variables to access &lt;code&gt;GET/POST/...&lt;/code&gt; values but rather used global variables.&lt;/p&gt;

&lt;p&gt;Finally, seeing that the situation didn’t improve, they made a drastic decision: to remove this functionality in PHP 5.4 to avoid all these problems. Thus, today, scripts like those we’ve seen (without using constants) are &lt;strong&gt;usually&lt;/strong&gt; no longer a risk, except for some harmless warnings/notices in certain cases.&lt;/p&gt;

&lt;h3&gt;
  
  
  Current Use
&lt;/h3&gt;

&lt;p&gt;Today, the constant technique is still common. However, the unfortunate reality — and the reason for this article — is that few developers understand the true reason behind its use.&lt;/p&gt;

&lt;p&gt;As with other best practices from the past (like copying parameters into local variables inside functions to avoid issues with references or using underscores in private variables to distinguish them), many continue to apply it simply because someone once told them it was a good practice, without questioning whether it still adds value today. The truth is that, in the &lt;strong&gt;majority&lt;/strong&gt; of cases, this technique is no longer necessary.&lt;/p&gt;

&lt;p&gt;Here are some reasons why this practice has lost relevance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Removal of *register globals&lt;/strong&gt;&lt;em&gt;: Since PHP 5.4, the automatic registration of &lt;code&gt;GET&lt;/code&gt; and &lt;code&gt;POST&lt;/code&gt; variables as globals in PHP was removed. Without *register globals&lt;/em&gt;, executing individual scripts directly is harmless, removing the main reason for this technique.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Better Code Design&lt;/strong&gt;: Even in pre-PHP 5.4 versions, modern code is better structured, generally in classes and functions, making access or manipulation via external variables more challenging. Even WordPress, which traditionally used global variables, minimizes these risks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use of *front-controllers&lt;/strong&gt;&lt;em&gt;: Nowadays, most web applications employ well-designed *front-controllers&lt;/em&gt; to ensure that class and function code only executes if the execution chain starts at the main entry point. Thus, if someone attempts to load files in isolation, the logic won’t trigger unless the flow starts from the correct entry point.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Class Autoloading&lt;/strong&gt;: With the widespread use of class autoloading in modern development, the use of &lt;code&gt;include&lt;/code&gt; or &lt;code&gt;require&lt;/code&gt; has significantly decreased. This reduces risks associated with these methods (like &lt;em&gt;Remote File Inclusion&lt;/em&gt; or &lt;em&gt;Local File Inclusion&lt;/em&gt;) among more experienced developers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Separation of Public and Private Code&lt;/strong&gt;: In many modern CMSs and frameworks, public code (like &lt;em&gt;assets&lt;/em&gt;) is separated from private code (logic). This measure is especially valuable, as it ensures that, if PHP fails on the server, PHP code (whether or not it uses the constant technique) is not exposed. Although this separation wasn’t implemented specifically to mitigate &lt;em&gt;register globals&lt;/em&gt;, it helps prevent other security issues.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Widespread Use of Friendly URLs&lt;/strong&gt;: Nowadays, configuring servers to use friendly URLs is common practice, ensuring a single entry point for application logic. This makes it nearly impossible for anyone to load PHP files in isolation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Error Suppression in Production&lt;/strong&gt;: Most modern CMSs and frameworks disable error output by default, so attackers don’t find clues about the application’s inner workings, which could facilitate other types of attacks.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even though this technique is no longer necessary in the &lt;strong&gt;majority&lt;/strong&gt; of cases, that doesn’t mean it’s never useful. As a professional developer, it’s essential to analyze each situation and decide whether the constant technique is relevant to the specific context in which you’re working. This kind of critical thinking should always be applied, even to so-called best practices.&lt;/p&gt;

&lt;h3&gt;
  
  
  Not Sure? Here are Some Tips
&lt;/h3&gt;

&lt;p&gt;If you’re still unsure when to apply the constant technique, these recommendations may guide you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Always use it&lt;/strong&gt; if you think your code might run on a PHP version earlier than 5.4.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don’t use it&lt;/strong&gt; if the file only contains a class definition.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don’t use it&lt;/strong&gt; if the file contains only functions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don’t use it&lt;/strong&gt; if the file only includes HTML/CSS, unless the HTML reveals sensitive information.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don’t use it&lt;/strong&gt; if the file only contains constants.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For everything else, if you’re in doubt, apply it. In most cases, it won’t be harmful and could protect you in unexpected circumstances, especially if you’re starting out. With time and experience, you’ll be able to assess when to apply this and other techniques more effectively.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffzrrq0iq0cpv3p1xcz84.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffzrrq0iq0cpv3p1xcz84.png" alt="Desarrollo PHP" width="512" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep Learning...
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.mediawiki.org/wiki/Register_globals" rel="noopener noreferrer"&gt;register_globals - MediaWiki&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://php.adamharvey.name/manual/en/security.globals.php" rel="noopener noreferrer"&gt;PHP: Using Register Globals - Manual&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lwn.net/Articles/203904/" rel="noopener noreferrer"&gt;Remote file inclusion vulnerabilities [LWN.net]&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://seclists.org/bugtraq/2001/Jul/569" rel="noopener noreferrer"&gt;Bugtraq: Serious security hole in Mambo Site Server version 3.0.X&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>php</category>
      <category>wordpress</category>
      <category>symfony</category>
      <category>laravel</category>
    </item>
    <item>
      <title>Ese extraño código PHP en frameworks y CMS</title>
      <dc:creator>Manuel Canga</dc:creator>
      <pubDate>Sun, 10 Nov 2024 10:29:35 +0000</pubDate>
      <link>https://dev.to/manuelcanga/ese-extrano-codigo-php-en-frameworks-y-cms-472i</link>
      <guid>https://dev.to/manuelcanga/ese-extrano-codigo-php-en-frameworks-y-cms-472i</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F29uo78bdjiemltqv4l7e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F29uo78bdjiemltqv4l7e.png" alt="Desarrollo web" width="512" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nota:&lt;/strong&gt; para seguir este post se presupone que dispones de unos conocimientos mínimos de programación en PHP.&lt;/p&gt;

&lt;p&gt;Esta entrada trata sobre un fragmento de código PHP que habrás visto en la parte superior de tu CMS o framework favorito y del cual probablemente hayas leído que debes incluir siempre, por seguridad, en la cabecera de todos los archivos PHP que desarrolles, aunque sin una explicación muy clara del porqué. Me refiero a este código:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;defined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'ABSPATH'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Exit if accessed directly&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Este tipo de código es muy común en los archivos de WordPress, aunque en realidad aparece en casi todos los frameworks y CMS. En el caso del CMS &lt;strong&gt;Joomla&lt;/strong&gt;, por ejemplo, lo único que cambia es que, en lugar de &lt;code&gt;ABSPATH&lt;/code&gt;, se usa &lt;code&gt;JEXEC&lt;/code&gt;. Por lo demás, la lógica es la misma. Este CMS surgió a partir de otro llamado &lt;strong&gt;Mambo&lt;/strong&gt;, que también usaba un código similar, pero con &lt;code&gt;_VALID_MOS&lt;/code&gt; como constante. Si retrocedemos aún más en el tiempo, encontraremos que el primer CMS en usar este tipo de código fue &lt;strong&gt;PHP-Nuke&lt;/strong&gt; (considerado por algunos como el primer CMS en PHP).&lt;/p&gt;

&lt;p&gt;El flujo de ejecución de &lt;strong&gt;PHP-Nuke&lt;/strong&gt; (y de la mayoría de CMS y frameworks hoy en día) consistía en cargar de manera secuencial varios archivos que, en conjunto, daban respuesta a la acción realizada por el usuario o visitante en la web. Es decir, imagina un sitio web de esa época, bajo el dominio example.net y con este CMS instalado. Cada vez que se cargaba la página de inicio, el sistema ejecutaba una secuencia de archivos de manera ordenada (en este caso es solo un ejemplo, no es una secuencia real): &lt;code&gt;index.php =&amp;gt; load_modules.php =&amp;gt; modules.php&lt;/code&gt;. Es decir, en esta sucesión, primero se cargaba &lt;code&gt;index.php&lt;/code&gt;, luego este script cargaba &lt;code&gt;load_modules.php&lt;/code&gt;, y este a su vez &lt;code&gt;modules.php&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Esta cadena de ejecución no siempre empezaba por el primer archivo (&lt;code&gt;index.php&lt;/code&gt;). De hecho, cualquiera podía saltarse parte de este flujo llamando directamente a uno de los otros archivos PHP por su URL (por ejemplo, &lt;code&gt;http://example.net/load_modules.php&lt;/code&gt; o &lt;code&gt;http://example.net/modules.php&lt;/code&gt;), lo cual, como veremos, podía ser peligroso en muchos casos.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;¿Cómo se resolvió este problema?&lt;/strong&gt; Se introdujo una medida de seguridad, agregando códigos al inicio de cada archivo, similares a este:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;eregi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"modules.php"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$HTTP_SERVER_VARS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'PHP_SELF'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;die&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"You can't access this file directly..."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Básicamente, este código, situado en la cabecera de un archivo llamado &lt;code&gt;modules.php&lt;/code&gt;, verificaba si se estaba accediendo a &lt;code&gt;modules.php&lt;/code&gt; directamente a través de la URL. Si era así, se detenía la ejecución mostrando el mensaje: "You can't access this file directly…". Si &lt;code&gt;$HTTP_SERVER_VARS['PHP_SELF']&lt;/code&gt; no contenía &lt;code&gt;modules.php&lt;/code&gt;, entonces significaba que estábamos en el flujo normal de ejecución y se permitía continuar.&lt;/p&gt;

&lt;p&gt;Este código, sin embargo, presentaba algunas limitaciones. Primero, el código era distinto para cada archivo en el que se insertaba, lo que añadía complejidad. Además, en ciertas circunstancias, PHP no asignaba un valor a &lt;code&gt;$HTTP_SERVER_VARS['PHP_SELF']&lt;/code&gt;, lo cual limitaba su efectividad.&lt;/p&gt;

&lt;p&gt;Entonces, ¿qué hicieron los desarrolladores? Sustituyeron todos esos fragmentos de código por una versión más sencilla y eficiente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;defined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'MODULE_FILE'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;die&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"You can't access this file directly..."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;En este nuevo código, que ya era bastante común en la comunidad PHP, se verificaba la existencia de una constante. Esta constante se definía y asignaba un valor en el primer archivo del flujo (&lt;code&gt;index.php&lt;/code&gt; o &lt;code&gt;home.php&lt;/code&gt; o algún archivo similar). Por lo tanto, si esta constante no existía en algún otro archivo de la secuencia, significaba que alguien había saltado el archivo &lt;code&gt;index.php&lt;/code&gt; y estaba intentando acceder a otro archivo directamente.&lt;/p&gt;

&lt;h2&gt;
  
  
  Peligros de que alguien lance un archivo PHP directamente
&lt;/h2&gt;

&lt;p&gt;Seguramente a estas alturas estés pensando que romper la cadena de ejecución debe de ser lo más grave del mundo. Sin embargo, la realidad es que, &lt;strong&gt;normalmente&lt;/strong&gt;, no representa un peligro importante.&lt;/p&gt;

&lt;p&gt;El peligro puede surgir cuando un error de PHP expone la ruta a nuestros archivos. Esto no debería preocuparnos si en el servidor tenemos configurada la supresión de errores, y, aunque los errores no estuvieran ocultos, la información expuesta sería mínima, proporcionando apenas algunas pistas a un posible atacante.&lt;/p&gt;

&lt;p&gt;También podría ocurrir que alguien accediera a archivos que contuvieran fragmentos de HTML (de vistas), revelando parte de su contenido. En la mayoría de los casos, esto tampoco debería ser motivo de preocupación.&lt;/p&gt;

&lt;p&gt;Finalmente, podría suceder que un desarrollador, bien por despiste o por falta de experiencia, inserte código peligroso y sin dependencia externa en medio de un flujo de ejecución. Esto es muy poco común, ya que normalmente el código de un framework o CMS depende de otras clases, funciones o variables externas para su ejecución. Por lo tanto, si se intenta ejecutar un script directamente a través de la URL, generará errores al no encontrar estas dependencias y no continuará su ejecución.&lt;/p&gt;

&lt;p&gt;Entonces, ¿por qué añadir el código de la constante si apenas hay motivos de preocupación? La razón es la siguiente: "Este método también previene la inyección accidental de variables a través de un ataque a &lt;em&gt;register globals&lt;/em&gt;, impidiendo que el archivo PHP asuma que está dentro de la aplicación cuando realmente no lo está."&lt;/p&gt;

&lt;h2&gt;
  
  
  Register globals
&lt;/h2&gt;

&lt;p&gt;Desde los inicios de PHP, todas las variables enviadas a través de URLs (&lt;code&gt;GET&lt;/code&gt;) o de formularios (&lt;code&gt;POST&lt;/code&gt;) se convertían automáticamente en globales. Es decir, si se accedía al archivo &lt;code&gt;download.php?filepath=/etc/passwd&lt;/code&gt;, en el archivo &lt;code&gt;download.php&lt;/code&gt; (y en los que dependieran de él en el flujo de ejecución) se podía usar &lt;code&gt;echo $filepath;&lt;/code&gt; y el resultado sería &lt;code&gt;/etc/passwd&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Dentro de &lt;code&gt;download.php&lt;/code&gt;, no había forma de saber si la variable &lt;code&gt;$filepath&lt;/code&gt; había sido creada por un archivo previo en el flujo de ejecución o si alguien la había falsificado a través de la URL o con un &lt;code&gt;POST&lt;/code&gt;. Esto generaba grandes agujeros de seguridad. Veámoslo con un ejemplo, suponiendo que el archivo &lt;code&gt;download.php&lt;/code&gt; contiene el siguiente código:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;file_exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$filepath&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Content-Description: File Transfer'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Content-Type: application/octet-stream'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Content-Disposition: attachment; filename="'&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$filepath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'"'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Expires: 0'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Cache-Control: must-revalidate'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Pragma: public'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Content-Length: '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;filesize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$filepath&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nb"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Flush system output buffer&lt;/span&gt;
    &lt;span class="nb"&gt;readfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$filepath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El desarrollador probablemente pensó en implementar su código con un patrón &lt;a href="https://en.wikipedia.org/wiki/Front_controller" rel="noopener noreferrer"&gt;Front Controller&lt;/a&gt;, es decir, haciendo que todas las peticiones web pasaran a través de un único archivo de entrada (&lt;code&gt;index.php&lt;/code&gt;, &lt;code&gt;home.php&lt;/code&gt;, etc.). Este archivo se encargaría de inicializar la sesión, cargar variables comunes, y finalmente, redirigir la petición a un script específico (en este caso &lt;code&gt;download.php&lt;/code&gt;) para realizar la descarga del archivo.&lt;/p&gt;

&lt;p&gt;Sin embargo, un atacante podría saltarse la secuencia de ejecución planeada, simplemente llamando a &lt;code&gt;download.php?filepath=/etc/passwd&lt;/code&gt; como se mencionaba antes. Así, PHP crearía automáticamente la variable global &lt;code&gt;$filepath&lt;/code&gt; con el valor &lt;code&gt;/etc/passwd&lt;/code&gt;, permitiendo al atacante descargar ese archivo del sistema. Grave error.&lt;/p&gt;

&lt;p&gt;Esto es solo la punta del iceberg, pues se podían ejecutar ataques aún más peligrosos con un esfuerzo mínimo. Por ejemplo, en un código como el siguiente, que el programador podría haber dejado como script sin terminar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;require_once&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$base_path&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"/My.class.php"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Un atacante podía ejecutar cualquier código usando un ataque de tipo &lt;a href="https://en.wikipedia.org/wiki/Remote_File_Inclusion" rel="noopener noreferrer"&gt;Remote File Inclusion (RFI)&lt;/a&gt;. Así, si el atacante creaba un archivo &lt;code&gt;My.class.php&lt;/code&gt; en su propio sitio &lt;code&gt;https://mysite.net&lt;/code&gt; con cualquier código que deseara ejecutar, podía llamar al script vulnerable pasándole su dominio: &lt;code&gt;codigo_inutil.php?base_path=https://mysite.net&lt;/code&gt;, y el ataque quedaba completado.&lt;/p&gt;

&lt;p&gt;Otro ejemplo: en un script llamado &lt;code&gt;remove_file.inc.php&lt;/code&gt; con el siguiente código:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;file_exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$filename&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nb"&gt;unlink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Fichero borrado"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;un atacante podría llamar directamente a este archivo usando una URL como &lt;code&gt;remove_file.inc.php?filename=/etc/hosts&lt;/code&gt;, y así intentaría borrar el archivo &lt;code&gt;/etc/hosts&lt;/code&gt; del sistema (si el sistema lo permite, o bien, otros archivos a los que tenga permisos de eliminación).&lt;/p&gt;

&lt;p&gt;En un CMS como WordPress, que además utiliza variables globales internamente, este tipo de ataques era devastador. Sin embargo, gracias a la técnica de la constante, estos y otros scripts PHP estaban protegidos. Veámoslo con el último ejemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;defined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'ABSPATH'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Exit if accessed directly&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;file_exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$filename&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nb"&gt;unlink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Fichero borrado"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ahora, si alguien intentara acceder a &lt;code&gt;remove_file.inc.php?filename=/etc/hosts&lt;/code&gt;, la constante bloquearía el acceso. Es fundamental que sea una constante, porque si fuera una variable, como es lógico, el atacante podría inyectarla.&lt;/p&gt;

&lt;p&gt;A estas alturas seguramente te preguntarás por qué PHP mantuvo esta funcionalidad si era tan peligrosa. Además, si conoces otros lenguajes de scripting (JSP, Ruby, etc.), verás que no tienen algo similar (por eso mismo tampoco usan la técnica de la constante). Recordemos que PHP nació como un sistema de plantillas en C, y este comportamiento facilitaba el desarrollo. La buena noticia es que, viendo los problemas que causaba, los mantenedores de PHP decidieron introducir una directiva en &lt;code&gt;php.ini&lt;/code&gt; llamada &lt;code&gt;register_globals&lt;/code&gt; (activada por defecto) para permitir desactivar esta funcionalidad.&lt;/p&gt;

&lt;p&gt;Pero como los problemas persistían, la desactivaron por defecto. Aún así, muchos hosts seguían activándola por miedo a que los proyectos de sus clientes dejaran de funcionar, ya que mucho del código de aquella época no usaba las variables recomendadas &lt;code&gt;HTTP_*_VARS&lt;/code&gt; para acceder a los valores &lt;code&gt;GET/POST/...&lt;/code&gt;, sino las variables globales.&lt;/p&gt;

&lt;p&gt;Finalmente, viendo que la situación no cambiaba, tomaron una decisión drástica: eliminar esta funcionalidad en PHP 5.4 para evitar todos estos problemas. Así, hoy en día, los scripts como los que hemos visto (sin utilizar constantes) ya no suponen &lt;strong&gt;normalmente&lt;/strong&gt; un peligro, salvo por alguna advertencia/notice inofensiva en ciertos casos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Uso hoy en día
&lt;/h2&gt;

&lt;p&gt;Hoy en día, la técnica de la constante sigue siendo común. Sin embargo, lo que resulta triste —y el motivo que originó esta publicación— es que pocos desarrolladores conocen la razón real de su uso.&lt;/p&gt;

&lt;p&gt;Como sucede con otras buenas prácticas del pasado (como copiar los parámetros en una función a variables locales para evitar peligros con las referencias en la llamada, o usar guiones bajos en variables privadas para distinguirlas), muchos lo siguen aplicando solo porque alguien alguna vez les dijo que era una buena práctica, sin considerar si realmente aporta valor en los tiempos actuales. La realidad es que, en la &lt;strong&gt;mayoría&lt;/strong&gt; de los casos, esta técnica ya no es necesaria.&lt;/p&gt;

&lt;p&gt;Algunas razones por las que esta práctica ha perdido relevancia son las siguientes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Desaparición de *register globals&lt;/strong&gt;&lt;em&gt;: Desde PHP 5.4, ya no existe la funcionalidad de registrar variables &lt;code&gt;GET&lt;/code&gt; y &lt;code&gt;POST&lt;/code&gt; como variables globales de PHP. Como hemos visto, sin *register globals&lt;/em&gt;, la ejecución de scripts individuales se vuelve inofensiva, eliminando la principal razón de esta práctica.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mejor diseño en el código actual&lt;/strong&gt;: Aun en versiones anteriores a PHP 5.4, el código moderno está mejor diseñado, estructurado en clases y funciones, lo que complica el acceso o la manipulación mediante variables externas. Incluso WordPress, que suele utilizar variables globales, minimiza estos riesgos.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Uso de *front-controllers&lt;/strong&gt;&lt;em&gt;: Hoy en día, la mayoría de las aplicaciones web utilizan *front-controllers&lt;/em&gt; bien diseñados, que garantizan que el código de las clases y funciones solo se ejecute si la cadena de ejecución comienza en el punto de entrada principal. Así, no importa si alguien intenta cargar archivos de forma aislada: la lógica no se activará si no se inicia el flujo desde el punto adecuado.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Autocarga de clases&lt;/strong&gt;: Dado que en el desarrollo actual se utiliza la autocarga de clases, el uso de &lt;code&gt;include&lt;/code&gt; o &lt;code&gt;require&lt;/code&gt; se ha reducido considerablemente. Esto hace que, salvo que seas un desarrollador novato, no debería haber &lt;em&gt;includes&lt;/em&gt; o &lt;em&gt;requires&lt;/em&gt; que puedan presentar riesgos (como &lt;em&gt;Remote File Inclusion&lt;/em&gt; o &lt;em&gt;Local File Inclusion&lt;/em&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Separación de código público y privado&lt;/strong&gt;: En muchos CMS y frameworks modernos, el código público (como &lt;em&gt;assets&lt;/em&gt;) se separa del código privado (el de programación). Esta medida es especialmente valiosa, ya que garantiza que, si PHP falla en el servidor, el código de los archivos PHP (usen o no la técnica de la constante) no quede expuesto. Aunque esto no se implementó específicamente para mitigar &lt;em&gt;register globals&lt;/em&gt;, ayuda a evitar otros problemas de seguridad.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Uso extendido de URLs amigables&lt;/strong&gt;: Hoy en día, es habitual configurar el servidor para usar URLs amigables, lo cual fuerza siempre un único punto de entrada a la programación. Esto hace casi imposible que alguien pueda cargar archivos PHP de forma aislada.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Supresión de salida de errores en producción&lt;/strong&gt;: La mayoría de los CMS y frameworks modernos bloquean la salida de errores por defecto, de modo que los atacantes no encuentran pistas sobre el funcionamiento interno de la aplicación, algo que podría facilitarles otros tipos de ataque.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Aunque esta técnica ya no sea necesaria en la &lt;strong&gt;mayoría&lt;/strong&gt; de los casos, esto no significa que nunca sea útil. Como desarrollador profesional, es fundamental analizar cada caso y decidir si la técnica de la constante es relevante en el contexto específico en el que estás trabajando. Este es un criterio que deberías aplicar siempre, incluso en lo que consideras buenas prácticas.&lt;/p&gt;

&lt;h2&gt;
  
  
  ¿Dudas? Aquí tienes algunos consejos
&lt;/h2&gt;

&lt;p&gt;Si aún no tienes claro cuándo aplicar la técnica de la constante, estas recomendaciones pueden orientarte:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Usa la técnica siempre&lt;/strong&gt; si piensas que tu código puede ejecutarse en una versión de PHP anterior a la 5.4.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No la uses&lt;/strong&gt; si el archivo solo contiene la definición de una clase.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No la uses&lt;/strong&gt; si el archivo contiene únicamente funciones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No la uses&lt;/strong&gt; si el archivo solo incluye HTML/CSS, a menos que el HTML exponga algún tipo de información valiosa.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No la uses&lt;/strong&gt; si el archivo solo contiene constantes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Para todo lo demás, si tienes dudas, aplícala. En la mayoría de los casos no debería ser perjudicial y podría protegerte en circunstancias inesperadas, especialmente si estás empezando. Con el tiempo y la experiencia, serás capaz de evaluar mejor cuándo aplicar esta y otras técnicas.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffzrrq0iq0cpv3p1xcz84.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffzrrq0iq0cpv3p1xcz84.png" alt="Desarrollo PHP" width="512" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Sigue aprendiendo...
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.mediawiki.org/wiki/Register_globals" rel="noopener noreferrer"&gt;register_globals - MediaWiki&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://php.adamharvey.name/manual/es/security.globals.php" rel="noopener noreferrer"&gt;PHP: Usando Register Globals - Manual&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lwn.net/Articles/203904/" rel="noopener noreferrer"&gt;Remote file inclusion vulnerabilities [LWN.net]&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://seclists.org/bugtraq/2001/Jul/569" rel="noopener noreferrer"&gt;Bugtraq: Serious security hole in Mambo Site Server version 3.0.X&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>spanish</category>
      <category>php</category>
      <category>wordpress</category>
      <category>laravel</category>
    </item>
    <item>
      <title>Google Analytics and WPO Analyzers</title>
      <dc:creator>Manuel Canga</dc:creator>
      <pubDate>Tue, 17 Sep 2024 19:54:31 +0000</pubDate>
      <link>https://dev.to/manuelcanga/google-analytics-and-wpo-analyzers-5coa</link>
      <guid>https://dev.to/manuelcanga/google-analytics-and-wpo-analyzers-5coa</guid>
      <description>&lt;p&gt;Translation of my old post: &lt;a href="https://web.archive.org/web/20200513162746/https://trasweb.net/snippets/google-analytics-y-analizadores-wpo" rel="noopener noreferrer"&gt;Google Analytics and WPO Analyzers&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I’m increasingly seeing more criticism against Google PageSpeed Insights (and other WPO analyzers) because many find it contradictory that Google’s own flagship tracking service, &lt;a href="https://web.archive.org/web/20200513162746/https://analytics.google.com/analytics/web/" rel="noopener noreferrer"&gt;Google Analytics&lt;/a&gt;, is flagged as an error. "But it’s from the same company!", you can hear them say.&lt;/p&gt;

&lt;p&gt;Google Analytics, like other tracking services, consumes a lot of resources during a website's load. It’s commendable that a service like &lt;a href="https://web.archive.org/web/20200513162746/https://developers.google.com/speed/pagespeed/insights/?hl=es" rel="noopener noreferrer"&gt;Google PageSpeed Insights&lt;/a&gt; flags this so you can optimize it. To me, it would lose credibility as a WPO tool if it didn’t. However, I understand that someone who doesn’t know about optimization might blame the tool instead. It reminds me of Aesop’s fable, &lt;a href="https://en.wikipedia.org/wiki/The_Fox_and_the_Grapes" rel="noopener noreferrer"&gt;The Fox and the Grapes&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;One of the options used to optimize the Google Analytics script is to host it on your own server and set an expiration date so that browsers can cache it. This is something &lt;a href="https://web.archive.org/web/20200513162746/https://support.google.com/analytics/answer/1032389?hl=es" rel="noopener noreferrer"&gt;Google doesn’t recommend&lt;/a&gt;, which is understandable because it loses the ability to update its code whenever it wants. If you don't opt for this option, based on what Google says, you can easily overcome this by setting up a CRON job to download the Google Analytics script every few hours.&lt;/p&gt;

&lt;p&gt;Another option (which is fully compatible with the previous one), and the one I use, is to load the Google Analytics script when someone scrolls on the page. This might seem detrimental because it might make you think it won’t track all users. However, in my opinion, it will give a more accurate metric:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First, it won’t track those who are quick to click a link on your site and, upon realizing their mistake, leave immediately.&lt;/li&gt;
&lt;li&gt;It won’t track robots, spiders, or similar entities that present themselves as regular users (since they don’t send user-agent headers that identify their real nature).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Moreover, it’s an optimal option because the script will load once everything else is already loaded (so it won’t slow anything down) and transparently while the user is browsing your website.&lt;/p&gt;

&lt;p&gt;Here’s the JavaScript code that makes this possible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * Google Analytics and WPO Analyzers - WebPerf - Manuel Canga
 * From post: https://trasweb.net/snippets/google-analytics-and-wpo-analyzers
 */&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;is_analytics_loaded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;load_googleAnalytics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;is_analytics_loaded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GoogleAnalyticsObject&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="p"&gt;[]).&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;)},&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementsByTagName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parentNode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insertBefore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;script&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;//www.google-analytics.com/analytics.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ga&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;ga&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;create&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;UA-xxxx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;ga&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;send&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pageview&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;is_analytics_loaded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;


&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scroll&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollTop&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollTop&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;load_googleAnalytics&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From &lt;strong&gt;line 25&lt;/strong&gt; to &lt;strong&gt;line 29&lt;/strong&gt;, we tell the browser that when the visitor scrolls (the scroll event is triggered, and the scroll bar position is no longer at the top), the &lt;code&gt;load_googleAnalytics&lt;/code&gt; function should be executed. This function checks (&lt;strong&gt;lines 9 to 12&lt;/strong&gt;) through a flag whether Analytics has already been loaded on the current page. If not, the tracking script is loaded (&lt;strong&gt;lines 13 to 19&lt;/strong&gt;). Notice that in &lt;strong&gt;line 18&lt;/strong&gt;, the Google Analytics ID is inserted. Finally, in &lt;strong&gt;line 21&lt;/strong&gt;, the flag is activated to prevent the script from loading again.&lt;/p&gt;




&lt;p&gt;If you liked it, don’t forget to share.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webperf</category>
      <category>performance</category>
      <category>seo</category>
    </item>
    <item>
      <title>Google Analytics y Analizadores WPO</title>
      <dc:creator>Manuel Canga</dc:creator>
      <pubDate>Tue, 17 Sep 2024 19:49:23 +0000</pubDate>
      <link>https://dev.to/manuelcanga/google-analytics-y-analizadores-wpo-2j5l</link>
      <guid>https://dev.to/manuelcanga/google-analytics-y-analizadores-wpo-2j5l</guid>
      <description>&lt;p&gt;Este post ya fue publicado inicialmente en 2019 en &lt;a href="https://web.archive.org/web/20200513162746/https://trasweb.net/snippets/google-analytics-y-analizadores-wpo" rel="noopener noreferrer"&gt;Google Analytics y Analizadores WPO&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cada vez veo más voces críticas contra Google PageSpeed Insights(y otros[analizadores WPO porque consideran incongruente que el propio Google marque su servicio estrella de tracking, &lt;a href="https://web.archive.org/web/20200513162746/https://analytics.google.com/analytics/web/" rel="noopener noreferrer"&gt;Google Analytics&lt;/a&gt;, como un error. ¡Si es de la misma "casa"!, se les oye decir.&lt;/p&gt;

&lt;p&gt;Google Analytics, como otros servicios de tracking, se come muchos recursos en la carga de una web. Es de alabanza que un servicio como &lt;a href="https://web.archive.org/web/20200513162746/https://developers.google.com/speed/pagespeed/insights/?hl=es" rel="noopener noreferrer"&gt;Google PageSpeed Insights&lt;/a&gt; marque esto para que lo optimices. Para mí, sería una falta de credibilidad a su labor de herramienta &lt;a href="https://web.archive.org/web/20200513162746/https://trasweb.net/webperf" rel="noopener noreferrer"&gt;WPO&lt;/a&gt;, si no lo hiciera. Pero claro, comprendo que quien no entiende de optimización, le eche la culpa a la herramienta. Me recuerda a la fábula de &lt;a href="https://es.wikipedia.org/wiki/La_zorra_y_las_uvas" rel="noopener noreferrer"&gt;La zorra y las uvas&lt;/a&gt; de Esopo.&lt;/p&gt;

&lt;p&gt;Una de las opciones que se usan para optimizar el script de Google Analytics es alojarlo bajo el propio alojamiento y entonces añadirle una fecha de caducidad para que los navegadores puedan cachearlo. Es algo que &lt;a href="https://web.archive.org/web/20200513162746/https://support.google.com/analytics/answer/1032389?hl=es" rel="noopener noreferrer"&gt;Google no recomienda&lt;/a&gt;, como es normal, porque entonces pierde todo el control de poder actualizar su código cuando quiera. Si no te decantas por esta opción, por lo que dice Google, piensa que esto es fácilmente salvable poniendo una tarea CRON para que, cada x hora, se descargue el script de Google Analytics.&lt;/p&gt;

&lt;p&gt;Otra opción(perfectamente compatible con la anterior), que es la que yo uso, es cargar el script de Google Analytics cuando alguien hace scroll en la página. Esto puede parecer perjudicial porque da a pensar que no trackeará a todos los usuarios. Sin embargo, en mi opinión dará una métrica más correcta:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;En primer lugar, no trackeará a aquellos que sean de dedo rápido, que hayan clicado a un enlace de tu web y, nada más darse cuenta del error, la quiten.&lt;/li&gt;
&lt;li&gt;No trackeará robots, arañas o similares, que se presenten como usuarios ordinarios(ya que no mandan cabeceras de agente de usuario que identifiquen su naturaleza real).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Por otro lado, es una opción óptima, pues se cargará una vez que ya esté todo cargado(no ralentizará nada) y de manera transparente, mientras que el usuario está viendo tu página web.&lt;/p&gt;

&lt;p&gt;Aquí está el código, el JavaScript que lo hace posible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * Google Analytics y Anaizadores WPO - WebPerf - Manuel Canga
 * Perteneciente a post: https://trasweb.net/snippets/google-analytics-y-anaizadores-wpo
 */&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;is_analytics_loaded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;load_googleAnalytics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;is_analytics_loaded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GoogleAnalyticsObject&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="p"&gt;[]).&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;)},&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementsByTagName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parentNode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insertBefore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;script&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;//www.google-analytics.com/analytics.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ga&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;ga&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;create&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;UA-xxxx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;ga&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;send&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pageview&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;is_analytics_loaded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;


&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scroll&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollTop&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollTop&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;load_googleAnalytics&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Desde la &lt;strong&gt;línea 25&lt;/strong&gt; a la &lt;strong&gt;línea 29&lt;/strong&gt; le decimos al navegador que cuando el visitante haga scroll(se lance el evento scroll y posición de la barra de scroll diferente a la inicial) se lance la función &lt;code&gt;load_googleAnalytics&lt;/code&gt;. Esta función comprueba(&lt;strong&gt;líneas 9 a 12&lt;/strong&gt;) mediante una flag si se cargó ya o no, el Analytics en la página actual. Si no lo hizo, se carga el script de tracking(&lt;strong&gt;líneas 13 a 19&lt;/strong&gt;). Date cuenta de que en la &lt;strong&gt;línea 18&lt;/strong&gt; va el ID de Google Analytics. En la &lt;strong&gt;línea 21&lt;/strong&gt;, finalmente, activamos la flag para que no se vuelve a realizar la carga.&lt;/p&gt;




&lt;p&gt;Si te ha gustado, no te olvides de compartir.&lt;/p&gt;

</description>
      <category>spanish</category>
      <category>javascript</category>
      <category>webperf</category>
      <category>seo</category>
    </item>
    <item>
      <title>Optimización web con ETags. Ejemplo con WordPress</title>
      <dc:creator>Manuel Canga</dc:creator>
      <pubDate>Mon, 09 Sep 2024 06:09:29 +0000</pubDate>
      <link>https://dev.to/manuelcanga/optimizacion-web-con-etags-ejemplo-con-wordpress-31e0</link>
      <guid>https://dev.to/manuelcanga/optimizacion-web-con-etags-ejemplo-con-wordpress-31e0</guid>
      <description>&lt;p&gt;Optimización web con ETags. Ejemplo con WordPress&lt;/p&gt;

&lt;p&gt;Este post ya fue publicado inicialmente en 2019 en &lt;a href="https://web.archive.org/web/20200513163812/https://trasweb.net/webperf/optimizacion-web-con-etags" rel="noopener noreferrer"&gt;Optimización web con ETags. Ejemplo con WordPress&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiqyy684elscges2d2qh3.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiqyy684elscges2d2qh3.jpg" alt="Contenido cacheado" width="290" height="185"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hace tiempo que no escribo sobre optimización. Ya sabéis lo que me conocéis a qué fue debido. Sin embargo, no puedo dejar que vende humos de supuesto WPO me quiten de escribir sobre algo que me gusta. Así que, aquí tenéis un nuevo post.&lt;/p&gt;

&lt;p&gt;Seguro que te ha pasado. Llegas un día a tu lugar de trabajo, enciendes tu equipo, abres tu correo y después de un vistazo a este, abres un terminal y escribes: &lt;code&gt;git pull&lt;/code&gt;. La respuesta del terminal no se deja esperar: &lt;code&gt;Already up-to-date.&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;¿Has pensado alguna vez qué ocurre detrás de ese &lt;code&gt;git pull&lt;/code&gt;? Yo sí. Conjeturando, yo diría que al hacer un &lt;code&gt;git pull&lt;/code&gt; estás mandando al servidor, de manera transparente, la fecha del último cambio que tú tienes. El repositorio comprueba la fecha del último cambio que tú le envías contra la fecha del último cambio que él tiene, de manera que:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Si tu fecha es más antigua, te manda todos los push/cambios que se han realizado desde entonces. También te mandará, junto a esos cambios, en la fecha que se hicieron. Así, si escribieras otra vez &lt;code&gt;git pull&lt;/code&gt; mandarías la fecha del último de esos cambios y volvería todo a empezar.&lt;/li&gt;
&lt;li&gt;Si tu fecha corresponde con la fecha que tiene el repositorio para el último cambio, te devolverá que tienes todo al día.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Este procedimiento, que para mí era el más lógico, no es el real. El real es parecido, pero no exacto. Cada vez que se hace un push. El repositorio asocia un token(código alfanumérico identificativo, algo como &lt;code&gt;ae3d9735f280381d0d97d3cdb26066eb16f765a5&lt;/code&gt;) al último commit. Cuando haces un &lt;code&gt;git pull&lt;/code&gt; se compara el último token que tú tienes con la lista de token que él tiene. Si tu token es uno antiguo, te manda los cambios desde entonces con sus correspondientes tokens. Si el token era el último, te dirá que estás al día.&lt;/p&gt;

&lt;p&gt;Llegado a este punto, tú me dirás: Manuel, ¿pero este post no iba de optimizar webs con WordPress? Ciertamente, y sigue siendo. Tanto el primer caso presentado(el de la fecha), como el segundo(el del token) son formas de trabajar del protocolo HTTP. Veámoslo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Last-Modified
&lt;/h2&gt;

&lt;p&gt;Imagínate que tu navegador manda una petición a mi servidor para descargar el favicon de mi web. En la respuesta de mi servidor a tu navegador irá la cadena(o cabecera HTTP ): &lt;code&gt;Last-Modified: Thu, 29 Dec 2016 11:55:29 GMT&lt;/code&gt;. Con ella, mi servidor está informando a tu navegador de cuando se modificó el favicon por última vez. Así que el navegador, una vez descargada la imagen, la guardará en su caché con el metadado “Last-Modified” y valor &lt;code&gt;Thu, 29 Dec 2016 11:55:29 GMT&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Si pasados unos segundos, unos días o unos meses, te decides a entrar otra vez en mi web, tu navegador necesitará nuevamente el favicon de mi sitio. Sin embargo, recuerda que también tiene una copia de la imagen en su caché. ¿Cómo sabe si el favicon que tiene en caché es el último o debe bajárselo de nuevo? Simple, haciendo un “&lt;code&gt;git pull&lt;/code&gt;”. Es decir, el navegador vuelve a mandar una petición de favicon a mi servidor, pero informando que tiene una versión de la imagen de una determinada fecha. Hay dos respuestas posibles de mi servidor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;El favicon que se utiliza ahora en mi web es más nueva, por lo que mi servidor mandará la nueva imagen a tu navegador, junto con la nueva fecha de última modificación que tiene esta nueva imagen.&lt;/li&gt;
&lt;li&gt;El favicon que se utiliza ahora en mi web es igual a la fecha que le indica tu navegador. Es decir, que tanto la imagen del servidor como la imagen de caché del navegador son iguales. Entonces, mi servidor le indica a tu navegador que la imagen no se ha modificado (con el código HTTP 304 &lt;code&gt;Not Modified&lt;/code&gt;). Tu navegador entonces usa la imagen de la caché y se ahorra de tener que bajar de nuevo la imagen (ahorrando así muchos bytes de tu tarifa de datos).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  ETags
&lt;/h2&gt;

&lt;p&gt;Si recuerdas, al principio del post, te contaba que Git trabajaba con tokens para determinar cuando se realizó cambios. HTTP, además de la fecha de última modificación, permite trabajar con unos tokens llamados ETags (&lt;a href="https://web.archive.org/web/20200513163812/https://es.wikipedia.org/wiki/HTTP_ETag" rel="noopener noreferrer"&gt;De Entity Tags)&lt;/a&gt;. Un ETag es un código alfanumérico (como podría ser &lt;code&gt;5864f9b1-47e&lt;/code&gt;) sin formato predeterminado(es decir, el estándar HTTP no marca, o casi no marca, qué formato debe tener el token). Es el dueño de un sitio el que determina cuál será su formato.&lt;/p&gt;

&lt;p&gt;Por defecto, los servidores webs como Apache crean el ETag de cada archivo basado en su fecha de modificación (y algunas veces también el tamaño del archivo). Esto es redundante (la cabecera HTTP de fecha de última modificación se basa en el mismo criterio) y poco óptimo (porque se añade más información a las peticiones que no sirve de nada). Lo recomendable en ese caso es configurar tu servidor web para que no use ETags con archivos. Por ejemplo, para desactivar los ETags de archivos (o FileETags) para Apache, añade el siguiente código en tú.htacess: &lt;code&gt;FileETag None&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Seguro que te estarás preguntado que si el diálogo entre navegador/servidor usando un ETag es el mismo que hemos visto para la fecha de última modificación y usar ambas formas es poco óptima y redundante. ¿Para qué usar ETags?&lt;/p&gt;

&lt;p&gt;La fecha de modificación es suficiente para peticiones HTTP a archivos, pero con peticiones HTTP a páginas webs(HTML) se queda corta. Una página web depende de muchos factores/elementos interrelacionados(contenido, comentarios, estructura HTML, … ) y no de un único archivo. Por tanto, sería muy complicado encontrar una fecha de última modificación unificada para todos esos elementos. Se que lo digo es complicado de seguir, trataré de explicarlo de otra manera:&lt;/p&gt;

&lt;p&gt;Imagínate que asigno como fecha de modificación de la página web( HTTML ) de esta entrada, la fecha de modificación del texto de la entrada. Tu navegador al entrar cachearía esta página junto a la fecha de última modificación del post. Si vuelves a entrar un minuto más tarde, como el post no ha cambiado( y, por tanto, su fecha de modificación ), tu navegador volverá a usar la versión que tiene en caché. Si alguien me escribiera un comentario y tu volvieras a entrar, tu no verías el comentario. Pues el texto del post no ha cambiado, por tanto, la fecha de última modificación tampoco, así que tu navegador volvería a mostrarte la versión de su caché. Lo mismo pasaría si cambio el HTML y añado un nuevo CSS. El contenido del post no ha cambiado,la fecha tampoco, y tu navegador seguiría mostrando la versión del cache.&lt;/p&gt;

&lt;p&gt;Si en vez de trabajar con fechas de última modificación del post, asignamos a la página web del post un ETag con el siguiente formato: &lt;code&gt;{fecha_modificacion_contenido_post}_{fecha_ultimo_comentario_post}_{numero_version_del_tema_WP}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Al entrar tu navegador por primera vez en el post, guardará en caché la página web(HTML) con su ETag asociado como metadato. Si cambiara alguno de los criterios del token(la fecha de modificación del post, la fecha del último comentario o la versión del tema actual de WP ), el ETag asociado a la página web sería diferente. Por lo que si entraras de nuevo al post, mi servidor notificará que el ETag de tu navegador no es el último y le volverá a mandar toda la página web, junto con el nuevo ETag.&lt;/p&gt;

&lt;p&gt;Si nada hubiera cambiado, el token/ETag sería el mismo( tanto en navegador como en servidor ), por lo que al visitar la página con tu navegador, mi servidor le mandaría un 304 avisándole que nada ha cambiado( en términos WPO se dice que aún está fresca ) y que, por tanto, use la versión de su caché.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beneficio de Etags
&lt;/h2&gt;

&lt;p&gt;Algo que no he comentado hasta ahora son los beneficios de ETags. Aquí algunos de ellos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Menos datos transferido entre servidor/navegador. Eso significa un ahorro de datos en el usuario &lt;a href="https://web.archive.org/web/20200513163812/https://trasweb.net/webperf/sabes-cuanto-dinero-cuesta-visitar-tu-web" rel="noopener noreferrer"&gt;para que tu web no le salga tan cara a tus usuarios&lt;/a&gt; y también en el servidor (importante si tienes contratado el alojamiento basado en pago por cantidad de datos transferido).&lt;/li&gt;
&lt;li&gt;El servidor se ahorra el tener que generar el HTML, con todo lo que eso implica: ahorro de memoria y CPU y liberar a la base de datos de trabajo.&lt;/li&gt;
&lt;li&gt;Una carga mucho más rápida de tu sitio web, mejorando con ello la experiencia del usuario.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Plugin WordPress
&lt;/h2&gt;

&lt;p&gt;Todo lo que hemos visto es a alto nivel, vamos a ver un pequeño plugin que use ETag para páginas/posts de WordPress.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;# etags.php
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;trasweb\webperf\ETags&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/*
 * Plugin Name:       ETags en posts
 * Plugin URI:        https://trasweb.net/webperf/optimizacion-web-con-etags
 * Description:       Usa el cache en navegador para tus posts.
 * Version:           0.0.1
 * Author:            Manuel Canga / Trasweb
 * Author URI:        https://trasweb.net
 * License:           GPL
 */&lt;/span&gt;

&lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'wp'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;is_admin&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nf"&gt;is_singular&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nv"&gt;$etag_from_navigator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$_SERVER&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s1"&gt;'HTTP_IF_NONE_MATCH'&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;??&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$current_ETag&lt;/span&gt;        &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_current_ETag&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$etag_from_navigator&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nv"&gt;$current_ETag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;status_header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;304&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ETag: '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$current_ETag&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;get_current_ETag&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$last_modified_time_of_content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;get_post_time&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$date_of_last_comment&lt;/span&gt;          &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_date_of_last_comment&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$theme_version&lt;/span&gt;                 &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;wp_get_theme&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt; &lt;span class="s2"&gt;"Version"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;??&lt;/span&gt;&lt;span class="s1"&gt;'0.0.0'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;md5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$last_modified_time_of_content&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$date_of_last_comment&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$theme_version&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;get_date_of_last_comment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'post_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;get_the_ID&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'orderby'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'comment_date_gmt'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="s1"&gt;'status'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'approve'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'order'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'DESC'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'number'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="nv"&gt;$last_comment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_comments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;??&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$last_comment&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;comment_date_gmt&lt;/span&gt;&lt;span class="o"&gt;??&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Antes que nada decir que este plugin es sólo formativo. Como con cualquier técnica de optimización web, como minificación/unificación de recursos CSS/JS o el uso de caché en servidor, se requiere primero un estudio del sitio.&lt;/p&gt;

&lt;p&gt;Como se puede ver más sencillo no puede ser. Primero se obtiene el ETag del navegador si es que lo hubiera( línea 20 ). En segundo lugar se obtiene el Etag asociado al post/page actual( línea 21 ).&lt;/p&gt;

&lt;p&gt;Si ambos son iguales se manda al navegador un código 304( línea 24, este es el caso que muestra la imagen principal de este post ) y se acaba la ejecución. Al navegador le llegará el código 304 y sabrá que tiene que hacer uso de la versión de la página de su caché.&lt;/p&gt;

&lt;p&gt;Si los Etags son diferentes( bien porque entra el navegador por primera vez o bien porque el token ha cambiado ), se manda el ETag al navegador y se deja que WP siga su curso( que manda el contenido del post/page actual ).&lt;/p&gt;

&lt;p&gt;El Etag se genera en la funcion get_current_ETag( línea 31 a 38 ) basado en la última vez que se modificó el post/page, la fecha del último comentario del post y la versión del tema actual. Si algunos de esos parámetros cambia, cambiara el token, forzando al navegador a bajar la nueva versión de la web.&lt;/p&gt;

&lt;p&gt;Esto es todo. Espero que os haya gustado y os sirva para hacer más rápida vuestra página web.&lt;/p&gt;




&lt;p&gt;Porfa, compártelo&lt;/p&gt;

</description>
      <category>spanish</category>
      <category>wordpress</category>
      <category>php</category>
      <category>webperf</category>
    </item>
    <item>
      <title>Web Optimization with ETags: An Example with WordPress</title>
      <dc:creator>Manuel Canga</dc:creator>
      <pubDate>Mon, 09 Sep 2024 05:56:30 +0000</pubDate>
      <link>https://dev.to/manuelcanga/web-optimization-with-etags-an-example-with-wordpress-5892</link>
      <guid>https://dev.to/manuelcanga/web-optimization-with-etags-an-example-with-wordpress-5892</guid>
      <description>&lt;p&gt;English version of my old post &lt;a href="https://web.archive.org/web/20200513163812/https://trasweb.net/webperf/optimizacion-web-con-etags" rel="noopener noreferrer"&gt;Optimización web con ETags. Ejemplo con WordPress&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbnugtvyw92saeoj34ve1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbnugtvyw92saeoj34ve1.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's been a while since I last wrote about optimization. Those who know me are aware of why that happened. However, I can't let so-called WPO (Web Performance Optimization) experts stop me from writing about something I enjoy. So, here's a new post for you.&lt;/p&gt;

&lt;p&gt;I'm sure this has happened to you. You arrive at your workplace, turn on your computer, open your email, and after checking it, open a terminal and type: &lt;code&gt;git pull&lt;/code&gt;. The terminal quickly responds: &lt;code&gt;Already up-to-date.&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Have you ever wondered what happens behind that &lt;code&gt;git pull&lt;/code&gt;? I have. If I had to guess, I'd say that when you do a &lt;code&gt;git pull&lt;/code&gt;, you're transparently sending the server the date of the last change you have. The repository checks the date of the last change you send against the date of the last change it has, so:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If your date is older, it sends you all the pushes/changes that have occurred since then. It will also send you, along with those changes, the date they were made. So, if you typed &lt;code&gt;git pull&lt;/code&gt; again, you would send the date of the last of those changes, and everything would start again.&lt;/li&gt;
&lt;li&gt;If your date matches the date the repository has for the last change, it will tell you that everything is up-to-date.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This process, which seemed the most logical to me, is not the real one. The real one is similar but not exact. Every time a push is made, the repository associates a token (an alphanumeric identification code, something like &lt;code&gt;ae3d9735f280381d0d97d3cdb26066eb16f765a5&lt;/code&gt;) with the latest commit. When you do a &lt;code&gt;git pull&lt;/code&gt;, it compares the last token you have with the list of tokens it has. If your token is an old one, it sends the changes since then with their corresponding tokens. If the token was the latest, it tells you you're up-to-date.&lt;/p&gt;

&lt;p&gt;At this point, you might say: Manuel, wasn't this post supposed to be about optimizing websites with WordPress? Indeed, it is. Both the first case presented (the one with the date) and the second one (the one with the token) are ways of working in the HTTP protocol. Let's take a closer look.&lt;/p&gt;

&lt;h2&gt;
  
  
  Last-Modified
&lt;/h2&gt;

&lt;p&gt;Imagine your browser sends a request to my server to download the favicon of my website. In the response from my server to your browser, there will be a string (or HTTP header): &lt;code&gt;Last-Modified: Thu, 29 Dec 2016 11:55:29 GMT&lt;/code&gt;. This tells your browser when the favicon was last modified. So, once your browser downloads the image, it will store it in its cache with the metadata "Last-Modified" and the value &lt;code&gt;Thu, 29 Dec 2016 11:55:29 GMT&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If, after a few seconds, days, or months, you decide to visit my website again, your browser will need the favicon from my site again. However, it remembers it also has a copy of the image in its cache. How does it know if the favicon in its cache is the latest or if it needs to download it again? Simple, it performs a "git pull." That is, the browser sends a new request for the favicon to my server, indicating that it has a version of the image from a specific date. There are two possible responses from my server:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The current favicon on my website is newer, so my server will send the new image to your browser, along with the new last-modified date of this image.&lt;/li&gt;
&lt;li&gt;The current favicon on my website matches the date indicated by your browser. That is, both the server's image and the browser's cached image are the same. Then, my server informs your browser that the image has not been modified (with the HTTP 304 &lt;code&gt;Not Modified&lt;/code&gt; code). Your browser then uses the cached image, saving itself from having to download the image again (thus saving many bytes of your data plan).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  ETags
&lt;/h2&gt;

&lt;p&gt;If you remember, at the beginning of the post, I mentioned that Git uses tokens to determine when changes were made. HTTP, in addition to the last modified date, allows working with tokens called ETags (&lt;a href="https://web.archive.org/web/20200513163812/https://en.wikipedia.org/wiki/HTTP_ETag" rel="noopener noreferrer"&gt;Entity Tags&lt;/a&gt;). An ETag is an alphanumeric code (such as &lt;code&gt;5864f9b1-47e&lt;/code&gt;) with no predetermined format (the HTTP standard does not specify, or barely specifies, what format the token should have). The site owner determines the format.&lt;/p&gt;

&lt;p&gt;By default, web servers like Apache create the ETag for each file based on its modification date (and sometimes also the file size). This is redundant (the HTTP header for the last modified date is based on the same criteria) and not optimal (because it adds more information to requests that is of no use). In this case, it's advisable to configure your web server not to use ETags for files. For example, to disable file ETags (or FileETags) in Apache, add the following code to your .htaccess file: &lt;code&gt;FileETag None&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You might be wondering if the dialog between the browser/server using an ETag is the same as we've seen for the last-modified date and using both methods is inefficient and redundant. Why use ETags, then?&lt;/p&gt;

&lt;p&gt;The last-modified date is sufficient for HTTP requests for files, but it falls short for HTTP requests for web pages (HTML). A web page depends on many interrelated factors/elements (content, comments, HTML structure, etc.) and not just a single file. Therefore, it would be very complicated to find a unified last modified date for all these elements. I know this might be difficult to follow, so I'll try to explain it differently:&lt;/p&gt;

&lt;p&gt;Imagine I assign the modification date of this web page (HTML) to the modification date of the text of the post. When your browser visits the page, it caches the page along with the post's last-modified date. If you visit it again a minute later, since the post has not changed (and thus, its modification date hasn't changed), your browser will use the cached version. If someone writes a comment and you visit again, you wouldn't see the comment. Since the post's text hasn't changed, the modification date hasn't either, so your browser would show you the cached version again. The same would happen if I change the HTML and add a new CSS file. The post content hasn't changed, nor has the date, so your browser would still show the cached version.&lt;/p&gt;

&lt;p&gt;If, instead of working with last-modified dates for the post, we assign an ETag to the web page of the post with the following format: &lt;code&gt;{post_content_modification_date}_{post_last_comment_date}_{WP_theme_version_number}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;When your browser visits the post for the first time, it caches the web page (HTML) with its associated ETag as metadata. If any of the token criteria change (the post's modification date, the last comment date, or the current WP theme version), the ETag associated with the web page would be different. So, if you visit the post again, my server will notify you that your browser's ETag is not the latest, and it will resend the entire web page along with the new ETag.&lt;/p&gt;

&lt;p&gt;If nothing has changed, the token/ETag would be the same (in both the browser and the server), so when you visit the page with your browser, my server would send a 304 response, notifying it that nothing has changed (in WPO terms, it's still "fresh") and that it should use the cached version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits of ETags
&lt;/h2&gt;

&lt;p&gt;Something I haven't mentioned until now are the benefits of ETags. Here are a few:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Less data transferred between the server and the browser. This means data savings for the user (so your website is less expensive for your users &lt;a href="https://web.archive.org/web/20200513163812/https://trasweb.net/webperf/sabes-cuanto-dinero-cuesta-visitar-tu-web" rel="noopener noreferrer"&gt;"How much does it cost to visit your website?"&lt;/a&gt;) and also for the server (important if you have a data-transfer-based hosting plan).&lt;/li&gt;
&lt;li&gt;The server saves the effort of generating the HTML, with all that implies: saving memory and CPU, and relieving the database of work.&lt;/li&gt;
&lt;li&gt;Much faster loading of your website, improving the user experience.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  WordPress plugin
&lt;/h2&gt;

&lt;p&gt;Everything we've covered is at a high level, so let's look at a small plugin that uses ETags for WordPress pages/posts.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;

# etags.php
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;trasweb\webperf\ETags&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/*
 * Plugin Name:       ETags en posts
 * Plugin URI:        https://trasweb.net/webperf/optimizacion-web-con-etags
 * Description:       Usa el cache en navegador para tus posts.
 * Version:           0.0.1
 * Author:            Manuel Canga / Trasweb
 * Author URI:        https://trasweb.net
 * License:           GPL
 */&lt;/span&gt;

&lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'wp'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;is_admin&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nf"&gt;is_singular&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nv"&gt;$etag_from_navigator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$_SERVER&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s1"&gt;'HTTP_IF_NONE_MATCH'&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;??&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$current_ETag&lt;/span&gt;        &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_current_ETag&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$etag_from_navigator&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nv"&gt;$current_ETag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;status_header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;304&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ETag: '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$current_ETag&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;get_current_ETag&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$last_modified_time_of_content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;get_post_time&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$date_of_last_comment&lt;/span&gt;          &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_date_of_last_comment&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$theme_version&lt;/span&gt;                 &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;wp_get_theme&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt; &lt;span class="s2"&gt;"Version"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;??&lt;/span&gt;&lt;span class="s1"&gt;'0.0.0'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;md5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$last_modified_time_of_content&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$date_of_last_comment&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$theme_version&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;get_date_of_last_comment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'post_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;get_the_ID&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'orderby'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'comment_date_gmt'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="s1"&gt;'status'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'approve'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'order'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'DESC'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'number'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="nv"&gt;$last_comment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_comments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;??&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$last_comment&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;comment_date_gmt&lt;/span&gt;&lt;span class="o"&gt;??&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;First of all, let me mention that this plugin is for educational purposes only. As with any web optimization technique, such as minification/combination of CSS/JS resources or using server-side caching, a site study is required first.&lt;/p&gt;

&lt;p&gt;As you can see, it couldn't be simpler. First, the ETag from the browser is obtained, if there is one (line 20). Second, the ETag associated with the current post/page is retrieved (line 21).&lt;/p&gt;

&lt;p&gt;If both are the same, a 304 code is sent to the browser (line 24, which is the case shown in the main image of this post), and execution ends. The browser will receive the 304 code and will know that it should use the cached version of the page.&lt;/p&gt;

&lt;p&gt;If the ETags are different (either because the browser is visiting for the first time or because the token has changed), the ETag is sent to the browser, and WordPress is allowed to continue its process (sending the content of the current post/page).&lt;/p&gt;

&lt;p&gt;The ETag is generated in the function get_current_ETag (lines 31 to 38) based on the last time the post/page was modified, the date of the last comment on the post, and the version of the current theme. If any of these parameters change, the token will change, forcing the browser to download the new version of the website.&lt;/p&gt;

&lt;p&gt;That's all. I hope you enjoyed this post and that it helps you make your website faster.&lt;/p&gt;




&lt;p&gt;Share it, please&lt;/p&gt;

</description>
      <category>php</category>
      <category>wordpress</category>
      <category>webperf</category>
      <category>performance</category>
    </item>
    <item>
      <title>WordPress es un CMS lento</title>
      <dc:creator>Manuel Canga</dc:creator>
      <pubDate>Thu, 05 Sep 2024 07:05:44 +0000</pubDate>
      <link>https://dev.to/manuelcanga/wordpress-es-un-cms-lento-26oi</link>
      <guid>https://dev.to/manuelcanga/wordpress-es-un-cms-lento-26oi</guid>
      <description>&lt;p&gt;Este post ya fue publicado inicialmente en 2014 en &lt;a href="https://web.archive.org/web/20170421013123/https://trasweb.net/blog/wpo/wordpress-es-un-cms-lento" rel="noopener noreferrer"&gt;WordPress es un CMS lento - 2014&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Más de una vez me he encontrado envuelto en el debate: ¿ es WordPress lento ?. Bueno, no es tanto debate cuando la única respuesta por parte de aquellos apegados a WordPress es que hay sitios con muchas visitas que lo tienen y su rendimiento es óptimo. Estos mismos parece que se olvidan que hasta el algoritmo de &lt;a href="https://es.wikipedia.org/wiki/Ordenamiento_de_burbuja" rel="noopener noreferrer"&gt;ordenación de burbuja&lt;/a&gt; se comporta bien para muestras excesivamente grandes si se "ejecuta" en un equipo potente. Sin embargo, eso no quiere decir que sea un algoritmo eficiente necesariamente (de hecho, no lo es) si atendemos a su &lt;a href="https://es.wikipedia.org/wiki/Teor%C3%ADa_de_la_complejidad_computacional" rel="noopener noreferrer"&gt;complejidad computacional&lt;/a&gt;. A WordPress le pasa lo mismo. A igualdad cantidad de información, requerirá un hosting mucho más potente que otros CMS. No sólo eso, sino como veremos, ya de por si es un CMS lento tenga o no tenga mucha información.&lt;/p&gt;

&lt;p&gt;Esto no quiere decir que WordPress sea malo. Nada más lejos de la realidad. Igual que en un coche, la velocidad no lo es todo. En el mundo de los CMS pasa lo mismo. De hecho, gran parte de mis proyectos webs son con él. Sin embargo, cada proyecto es un mundo y, por tanto, hay que saber escoger las mejores herramientas adecuadamente con la cabeza y no con el apego.&lt;/p&gt;

&lt;p&gt;Como soy una persona técnica, mis argumentos estarán basados en aspectos técnicos. Sobre todo cuando entiendo que WordPress es lento debido a su diseño. Invito a todos aquellos que no estén de acuerdo que me pongan un comentario con sus razonamientos.&lt;/p&gt;

&lt;h3&gt;
  
  
  Todo en una tabla.
&lt;/h3&gt;

&lt;p&gt;Cuando estamos realizando un esquema de base de datos para un proyecto web, surge la duda si ir por lo práctico o por lo eficiente. En el caso de WordPress tiraron por lo práctico y agruparon tanto los posts, como custom posts, como recursos y versiones en una misma tabla: &lt;a href="https://codex.wordpress.org/Database_Description#Table:_wp_posts" rel="noopener noreferrer"&gt;wp_posts&lt;/a&gt;. Esta acción tiene la ventaja de simplificar el código y las búsquedas (aunque esto sea otra cosa de la que cojea WordPress como ya veremos en otra entrada), pero como contraparte reduce drásticamente la eficiencia de WordPress. Algunos ejemplos para que se entienda:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Si tenemos 500 posts y cada uno tiene cuatro revisiones diferentes(la actual y tres más), es como si tuviéramos tratando con 2.000 posts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Si tenemos 500 productos con Woocommerce y cada uno tiene una imagen destacada y cuatro como galería de ese producto, es como si nuestro CMS tuviera que trabajar con 3.000 productos.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Si tenemos un sitio web pequeño con 35 páginas y en él tenemos unos 35 elementos de menús,ya sea, con enlaces externos o internos. Nuestro gestor de contenidos trabajaría como si tuviéramos 70 páginas. Ya que cada elemento de menú cuenta como si fuera una entrada o una página en nuestro CMS. En este ejemplo eso no es mucho, pero lo pongo para que se vea otro de los factores que influye.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Si tienes 500 productos y cuatro idiomas, tu WordPress es como si trabajara 2.000 productos.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ahora vamos a un ejemplo real a modo de resumen: Si tienes un sitio web con 500 productos y por cada uno de ellos también tienes una imagen destacada, cuatro imágenes de galería de producto y un pdf con información técnica de cada producto. Además, ese sitio también tiene blog con 200 entradas cada cual con sus correspondientes imágenes destacadas. Además, si has hecho el web con soporte a tres idiomas y estableciendo para que sólo haya dos revisiones por post. WordPress cada vez que lanza una consulta contra tu base de datos, ha de buscar entre más de 5.500 elementos. Desprecio otros como elementos de menú, páginas y custom posts. Consejos:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Limita el número de revisiones a dos o desactiva las revisiones completamente:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;    &lt;span class="c1"&gt;//Limita las revisiones a dos:&lt;/span&gt;
    &lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'WP_POST_REVISIONS'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;//Desactiva totalmente las revisiones:&lt;/span&gt;
    &lt;span class="c1"&gt;//define( 'WP_POST_REVISIONS', false );&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Borra todas las revisiones de vez en cuando. Puedes hacerlo lanzando la siguiente consulta sql:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;    &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;wp_posts&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
    &lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;wp_term_relationships&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;object_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;wp_postmeta&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'revision'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Se austero con las imágenes en tu sitio web. Tampoco añadas a tu CMS imágenes que no vayas a utilizar.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Evita tener multitud de menús si no son imprescindible. Elimina entradas de menús que no vayas a usar.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Si no te queda otra, porque así lo insiste tu cliente, que usar WordPress para proyectos medianos o grandes, trata de crear tablas auxiliares y así aligerar en lo posible la carga de wp_posts&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Tu WordPress tiene alzheimer
&lt;/h3&gt;

&lt;p&gt;WordPress busca la flexibilidad a cualquier precio, aunque sea a costa de velocidad. Quizás, porque en los principios sólo iba a ser un sistema de blogs y para ese caso tanta flexibilidad no podría causar tanto daño. Sin embargo, empezamos a usarlo como CMS y ahí empezaron los problemas de rendimiento ocasionados por la flexibilidad.&lt;/p&gt;

&lt;p&gt;Déjame decirte una mala noticia: tu gestor de contenidos tiene alzheimer. Se le olvida todo de una petición a otra. Tendrás que repetirle en cada una de ellas los customs posts, los sidebars, o los menús que vas a usar. No te queda otra porque a él se le olvida. Es por ello, que si quieres añadir una entrada al menú del panel, por ejemplo, se lo tendrás que decir cada vez que se vaya a mostrar. Sí, da una enorme flexibilidad pero obliga a PHP y al CMS a procesar lo mismo una vez y otra vez, dando lugar a una perdida de eficiencia. Lo mismo le pasa a los plugins y es por ello que muchos plugins pueden ralentizar sobremanera tu sitio web. No por el sistema de plugins en sí ( que es magnifico como está pensado y programado ), sino por la obligación de los plugins de decir lo mismo una y otra vez y, por tanto, la necesidad de WordPress de recorrerlos completamente en cada petición.&lt;/p&gt;

&lt;p&gt;Un CMS enfocado al rendimiento lo hubiera hecho de otra manera. Por ejemplo, haciendo que durante la activación del tema éste dijera que sidebars, custom posts o cualquier otro elemento necesita. Este CMS lo registraría y se ajustaría adecuadamente de manera interna. Y lo mismo para los plugins. Pero, como digo anteriormente, un proceder así restaría mucha flexibilidad al CMS, algo que a WordPress no le interesa.&lt;/p&gt;

&lt;h4&gt;
  
  
  Consejos:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Limita el número de plugins&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Escoge temas minimalistas o que sólo tengan lo que necesites&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Te recomendarán que uses un plugin de caché, yo no. Úsalo sólo en el caso que tu sitio web vaya extremadamente lento y siempre con pinzas. Hablaré de ello en otra entrada (edit: ya está disponible: &lt;a href="https://web.archive.org/web/20170421013123/https://trasweb.net/blog/optimizacion-web/no-uses-plugins-cache-con-wordpress" rel="noopener noreferrer"&gt;No uses plugins de caché con WordPress&lt;/a&gt; , aunque básicamente es porque cortarás todo el funcionamiento interno de WordPress basado en hooks. Es decir, forzarás a trabajar a WordPress de una manera, que como hemos visto, no es la que han decidido para él.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Todo siempre a tu disposición
&lt;/h3&gt;

&lt;p&gt;WordPress, como casi todo el mundo sabe, empezó como un sistema de blogs que se basaba en otro sistema anterior. No estaba pensado para proyectos grandes es por eso que su diseño tendió a lo simple. No clases, sólo funciones. Como cualquier aspecto de diseño, eso no tiene que ser algo necesariamente malo (sino que se lo digan a los que usan escritorios realizados con GTK), a no ser que busques la flexibilidad. Entonces, es cuando empiezan los dolores de cabeza.&lt;/p&gt;

&lt;p&gt;Si vienes del mundo PHP, seguramente te sorprendas que con WordPress no has tenido ni que hacer requires, ni includes ni usar namespace. Es algo sencillo de entender, el motivo es que WordPress siempre carga todo su arsenal de librerías. Sí, siempre, las uses o no. Si lo sumamos a que tiene alzheimer, uff. Líneas de código que se tienen que leer si o si en cada petición. Un pasote. Pero, claro, piensa que es por la flexibilidad. Puedes usar una función del core sin tener que hacer un include a un fichero que puede que el día de mañana tenga otro nombre o esté en otro path.&lt;/p&gt;

&lt;p&gt;A partir de PHP 5.6 hay soporte completo de namespace de funciones. Quizás esa sea una solución para WordPress. Pero en ese caso tendrán que tomar la difícil decisión de crear incompatibilidad hacia atrás. No sé lo que harán.&lt;/p&gt;

&lt;p&gt;Nada puedes hacer para mejorar esto, ya que es parte del diseño de WordPress. Tan sólo te queda hacer tu parte, es decir, que tu código no siga esa línea. Si te decides a hacerlo, aquí mis consejos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Crea funciones anónimas para los "actions" y que no sean más que un include a ficheros externos dónde tengas tu código. Así, si esa acción no llega nunca a lanzarse tampoco PHP tendrá que parsear todo el código. Ejemplo:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;    &lt;span class="nf"&gt;add\_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'admin_init'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;__DIR__&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"/zonas/panel/init.php"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;add\_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'admin_menu'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;__DIR__&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"/zonas/panel/menu.php"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Para widgets, shortcodes y filtros, usa clases con namespace. Además, que estás clases se instancien mediante autocarga.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;    &lt;span class="c1"&gt;//Recomendable usar mejor: spl_autoload_register() &lt;/span&gt;

    &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__autoload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$classname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;str_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;DIRECTORY_SEPARATOR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$classname&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;include_once&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;BASE_PATH&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'.php'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;add_shortcode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'block'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'misshortcodesBlock'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'load'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;//...mis otros shortcodes, filtros y widgets, ....&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Como resumen, hemos visto que WordPress tiene como principios de diseño a la simplicidad y flexibilidad, pero de una forma que le resta eficiencia. Hay que pensar que ninguna herramienta de desarrollo es buena para todo. Si alguien te lo presenta así es porque te está engañando o te vende una navaja suiza que no sirve para nada. WordPress cojea en su velocidad, pero para sitios webs escaparates es algo que no hay que darle mayor importancia. Para sitios en los que el negocio es la web se debería de plantear otras alternativas. Lo mismo para sitios con bastante tráfico. Si aún así queremos WordPress por su facilidad de uso y flexibilidad hemos de saber que habremos de compensarlo con un buen alojamiento, mucho cuidado con la selección de plugins y un tema de calidad y ajustado a nuestras necesidades.&lt;/p&gt;

</description>
      <category>spanish</category>
      <category>wordpress</category>
      <category>php</category>
      <category>webperf</category>
    </item>
    <item>
      <title>WordPress is a Slow CMS</title>
      <dc:creator>Manuel Canga</dc:creator>
      <pubDate>Thu, 05 Sep 2024 06:46:33 +0000</pubDate>
      <link>https://dev.to/manuelcanga/wordpress-is-a-slow-cms-16l0</link>
      <guid>https://dev.to/manuelcanga/wordpress-is-a-slow-cms-16l0</guid>
      <description>&lt;p&gt;English version of my old post &lt;a href="https://web.archive.org/web/20170421013123/https://trasweb.net/blog/wpo/wordpress-es-un-cms-lento" rel="noopener noreferrer"&gt;WordPress es un CMS lento - 2014&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;More than once, I've found myself in the middle of the debate: Is WordPress slow? Well, it’s not much of a debate when the only answer from those attached to WordPress is that there are sites with many visits using it, and their performance is optimal. These people seem to forget that even the bubble sort algorithm &lt;a href="https://en.wikipedia.org/wiki/Bubble_sort" rel="noopener noreferrer"&gt;Bubble sort&lt;/a&gt; performs well for excessively large samples if "run" on a powerful machine. However, this doesn't mean it is necessarily an efficient algorithm (it is not, in fact) if we consider its &lt;a href="https://en.wikipedia.org/wiki/Computational_complexity" rel="noopener noreferrer"&gt;computational complexity&lt;/a&gt;. The same thing happens with WordPress. Given the same amount of information, it will require a much more powerful hosting than other CMS. Not only that, but as we will see, WordPress is inherently slow whether it has a lot of information or not.&lt;/p&gt;

&lt;p&gt;This does not mean that WordPress is bad. Nothing could be further from the truth. Just like in a car, speed is not everything. The same goes for the world of CMS. In fact, many of my web projects are built with it. However, each project is different, and therefore, you need to choose the best tools wisely, not out of attachment.&lt;/p&gt;

&lt;p&gt;Since I am a technical person, my arguments will be based on technical aspects. Particularly when I understand that WordPress is slow due to its design. I invite anyone who disagrees to leave a comment with their reasoning.&lt;/p&gt;

&lt;h3&gt;
  
  
  All in One Table
&lt;/h3&gt;

&lt;p&gt;When designing a database schema for a web project, the question arises whether to go for practicality or efficiency. In the case of WordPress, they chose practicality and grouped posts, custom posts, resources, and versions all in one table: &lt;a href="https://codex.wordpress.org/Database_Description#Table:_wp_posts" rel="noopener noreferrer"&gt;wp_posts&lt;/a&gt;. This action has the advantage of simplifying the code and searches (although this is another issue WordPress struggles with, as we will see in another post), but on the downside, it drastically reduces WordPress's efficiency. Some examples to make this clearer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;If we have 500 posts, and each has four different revisions (the current one and three more), it’s as if we were dealing with 2,000 posts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If we have 500 products with WooCommerce, and each has a featured image and four in a product gallery, it’s as if our CMS had to handle 3,000 products.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If we have a small website with 35 pages and 35 menu items, whether external or internal links, our content manager would work as if there were 70 pages since each menu item counts as an entry or page in our CMS. This might not seem much in this example, but it shows another factor influencing performance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you have 500 products in four languages, your WordPress will act as if it were handling 2,000 products.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Now, let’s go to a real-world example in summary: If you have a website with 500 products, each with a featured image, four product gallery images, and a PDF with technical information, and the site also has a blog with 200 entries, each with their respective featured images. If your site also supports three languages and is set to allow only two revisions per post, WordPress must search through over 5,500 items every time it queries your database. I am ignoring other factors like menu items, pages, and custom posts. Advice:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Limit the number of revisions to two or disable them completely:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Limit revisions to two:&lt;/span&gt;
&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'WP_POST_REVISIONS'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Completely disable revisions:&lt;/span&gt;
&lt;span class="c1"&gt;// define('WP_POST_REVISIONS', false);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Delete all revisions from time to time. You can do this by running the following SQL query:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;wp_posts&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;wp_term_relationships&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;object_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;wp_postmeta&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'revision'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Be frugal with the images on your website. Do not add images to your CMS that you will not use.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Avoid having multiple menus unless they are essential. Remove menu entries you do not intend to use.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you have no other option, because your client insists on using WordPress for medium or large projects, try creating auxiliary tables to lighten the load on wp_posts as much as possible.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Your WordPress Has Alzheimer's
&lt;/h3&gt;

&lt;p&gt;WordPress seeks flexibility at any cost, even at the expense of speed. Maybe because in its beginnings, it was only going to be a blogging system, and in that case, so much flexibility wouldn’t cause much damage. However, when we started using it as a CMS, performance problems caused by this flexibility began to arise.&lt;/p&gt;

&lt;p&gt;Let me give you some bad news: your content manager has Alzheimer’s. It forgets everything from one request to another. You will have to repeat each time which custom posts, sidebars, or menus you are going to use. There is no other choice because it forgets. That's why, for example, if you want to add an entry to the admin menu, you will have to tell it every time it is to be displayed. Yes, it offers enormous flexibility, but it forces PHP and the CMS to process the same thing repeatedly, resulting in a loss of efficiency. The same thing happens with plugins, which is why many plugins can significantly slow down your website. It’s not because of the plugin system itself (which is magnificently designed and programmed) but because plugins have to declare the same information repeatedly, forcing WordPress to go through them entirely with every request.&lt;/p&gt;

&lt;p&gt;A performance-focused CMS would have done it differently. For example, by having the theme declare during activation what sidebars, custom posts, or other elements it needs. This CMS would register this information and adjust internally. The same could be applied to plugins. But, as mentioned earlier, such an approach would significantly reduce the CMS's flexibility, which for WordPress is not desirable.&lt;/p&gt;

&lt;h4&gt;
  
  
  Tips:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Limit the number of plugins.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Choose minimalist themes that only have what you need.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You might be advised to use a cache plugin; I don't. Only use one if your website is extremely slow and do so with caution. I will discuss this in another post (edit: now available: &lt;a href="https://web.archive.org/web/20170421013123/https://trasweb.net/blog/optimizacion-web/no-uses-plugins-cache-con-wordpress" rel="noopener noreferrer"&gt;Don’t Use Cache Plugins with WordPress&lt;/a&gt;, but basically, it’s because you will disable all of WordPress’s internal workings based on hooks. That is, you will force WordPress to work in a way that is not intended.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Everything Always Available
&lt;/h3&gt;

&lt;p&gt;As almost everyone knows, WordPress started as a blogging system based on a previous system. It wasn't designed for large projects, which is why its design leaned toward simplicity. No classes, just functions. As with any design aspect, this doesn’t have to be a bad thing (just ask those using desktops built with GTK) unless you are looking for flexibility. Then, the headaches begin.&lt;/p&gt;

&lt;p&gt;If you come from the PHP world, you might be surprised that with WordPress, you don’t have to use "require," "include," or "namespace." This is easy to understand: WordPress always loads its entire arsenal of libraries. Yes, always, whether you use them or not. When you combine this with its memory issues, well... that's a lot of code to read with every request. But, of course, this is all for flexibility. You can use a core function without having to include a file that might change names or paths tomorrow.&lt;/p&gt;

&lt;p&gt;Since PHP 5.6, there is full support for function namespaces. Maybe this is a solution for WordPress. But in that case, they will have to make the difficult decision of breaking backward compatibility. I don't know what they will do.&lt;/p&gt;

&lt;p&gt;There’s nothing you can do to improve this, as it’s part of WordPress’s design. All you can do is your part: make sure your code doesn't follow this path. If you decide to do so, here are my tips:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create anonymous functions for "actions" that are nothing more than includes to external files where you keep your code. This way, if the action never triggers, PHP won’t have to parse all the code. Example:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'admin_init'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;__DIR__&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"/zones/panel/init.php"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'admin_menu'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;__DIR__&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"/zones/panel/menu.php"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;For widgets, shortcodes, and filters, use classes with namespaces. Also, make sure these classes are instantiated using autoloading.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// It's better to use: spl_autoload_register()&lt;/span&gt;

&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__autoload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$classname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;str_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'\\'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;DIRECTORY_SEPARATOR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$classname&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;include_once&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;BASE_PATH&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$file&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'.php'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;add_shortcode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'block'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'myShortcodesBlock'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'load'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="c1"&gt;//... my other shortcodes, filters, and widgets...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In summary, we have seen that WordPress's design principles are simplicity and flexibility, but in a way that sacrifices efficiency. It is essential to understand that no development tool is good for everything. If someone presents it that way, they are either misleading you or selling you a Swiss army knife that is good for nothing.&lt;/p&gt;

&lt;p&gt;WordPress struggles with speed, but for showcase websites, this is not something to worry about. However, for websites where the business relies on the web, or for sites with a lot of traffic, alternative options should be considered. Still, if we choose WordPress for its ease of use and flexibility, we must compensate by investing in good hosting, being very careful with the selection of plugins, and using a high-quality theme tailored to our needs.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>webperf</category>
      <category>php</category>
      <category>performance</category>
    </item>
  </channel>
</rss>
