<?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: oskar calvo</title>
    <description>The latest articles on DEV Community by oskar calvo (@oskar_calvo_1615a9b3b293f).</description>
    <link>https://dev.to/oskar_calvo_1615a9b3b293f</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1765681%2F71ebf4c9-9794-4626-a552-fe301f82d5bd.jpg</url>
      <title>DEV Community: oskar calvo</title>
      <link>https://dev.to/oskar_calvo_1615a9b3b293f</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/oskar_calvo_1615a9b3b293f"/>
    <language>en</language>
    <item>
      <title>Sincronizar bloques con Block placeholder</title>
      <dc:creator>oskar calvo</dc:creator>
      <pubDate>Sun, 14 Jun 2026 11:46:53 +0000</pubDate>
      <link>https://dev.to/oskar_calvo_1615a9b3b293f/sincronizar-bloques-con-block-placeholder-557</link>
      <guid>https://dev.to/oskar_calvo_1615a9b3b293f/sincronizar-bloques-con-block-placeholder-557</guid>
      <description>&lt;p&gt;Uno de las cosas que tenemos que entender cuando trabajamos con Drupal es que tenemos algunas entidades que funcionan a la vez como contenido y como entidad de configuración.&lt;/p&gt;

&lt;p&gt;Drupal tiene un sistema de sincronización de configuraciones que es muy potente, y a su vez tiene módulos que extienden la sincronización de estas entidades con esa doble característica.&lt;/p&gt;

&lt;p&gt;Existen varios de estos módulos, algunos de ellos los he probado en el pasado y reconozco que no se como están en 2025; vaya esto por delante.&lt;/p&gt;

&lt;p&gt;Una de las cosas que vi en su día es que la sincronización funcionaba bien excepto cuando estas entidades llevaban asociadas files, en ese momento teníamos el problema de que la configuración generaba el yml pero el recurso multimedia que se había asociado no se sincronizaba por lo que o se subía a mano (no siempre se pueden subir a mano a según que servidores) o teníamos un problema.&lt;/p&gt;

&lt;p&gt;El módulo &lt;a href="https://www.drupal.org/project/block_placeholder" rel="noopener noreferrer"&gt;block placeholder&lt;/a&gt; nos ayuda a solucionar en cierta manera esto, &lt;/p&gt;

&lt;h2&gt;
  
  
  ¿Cómo lo hace Block Placeholder?
&lt;/h2&gt;

&lt;p&gt;Genera un tipo de bloque que es únicamente configuración, no se le puede añadir campos, únicamente un nombre y definir que ese placeholder se pueda usar por uno o más bloques a la vez; esto sería que el placeholder pinta un único bloque o que pinte uno, dos, tres... bloques, y también definimos que tipos de bloque vamos a usar con ese placeholder.&lt;/p&gt;

&lt;p&gt;Aquí un ejemplo del yml que genera el módulo de una entidad de configuración de block placeholder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;uuid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cf4113bd-c132-456a-aa59-b609009f81cd&lt;/span&gt;
&lt;span class="na"&gt;langcode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;en&lt;/span&gt;
&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;  &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;imagen_de_prueba_placeholder&lt;/span&gt;
&lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Imagen&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;de&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;prueba&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;placehodler'&lt;/span&gt;
&lt;span class="na"&gt;reference_limit_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unlimited&lt;/span&gt;
&lt;span class="na"&gt;reference_limited_value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="na"&gt;block_types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;imagen&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;imagen&lt;/span&gt;
&lt;span class="na"&gt;sequence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Una vez tenemos creaco el placeholder, lo único que tenemos que hacer es crear el bloque en &lt;code&gt;admin/content/block&lt;/code&gt; o editarlo si lo tenemos creado y  en la sección de placeholder identificar el placeholder con el que queremos relacionarlo. &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%2Fjr2y1zfocpaxp3tm0g81.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%2Fjr2y1zfocpaxp3tm0g81.png" alt=" " width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Los peros
&lt;/h2&gt;

&lt;p&gt;Al principio suele ser tedioso andar creando en un entorno los bloques de tipo contenido, y luego tener que asociarlos a los placesholder, pero una vez que un bloque de contenido está creado se puede asociar al placeholder de forma manual o se puede usar un hook_post_update o hook_deploy para sincronizar la relación entre bloques de contenido y placeholder si fuera necesario.&lt;/p&gt;

&lt;p&gt;En mi caso lo he empezado a usar en páginas que se construyen con la entidad views de drupal y el bloque va fuera de la vista, en una región superior, así que la configuración es añadir el placeholder y en el entorno de producción se configura la relación entre el placeholder y el bloque con el contenido.&lt;/p&gt;

&lt;p&gt;Se puede usar también en bloques que no aparecen en &lt;code&gt;admin/content/block&lt;/code&gt; pero en ese caso no veo tanta necesidad de hacerlo. &lt;/p&gt;

</description>
      <category>drupal</category>
      <category>blocks</category>
      <category>sync</category>
    </item>
    <item>
      <title>Usar h2non/imaginary con Ddev</title>
      <dc:creator>oskar calvo</dc:creator>
      <pubDate>Sun, 14 Jun 2026 11:40:31 +0000</pubDate>
      <link>https://dev.to/oskar_calvo_1615a9b3b293f/usar-h2nonimaginary-con-ddev-3e6h</link>
      <guid>https://dev.to/oskar_calvo_1615a9b3b293f/usar-h2nonimaginary-con-ddev-3e6h</guid>
      <description>&lt;p&gt;Se tiene que crear el archivo docker-compose.imaginary.yaml dentro de .ddev y añadir el siguiente contenido:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;imaginary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;h2non/imaginary:latest&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ddev-${DDEV_PROJECT}-imaginary"&lt;/span&gt;
    &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;com.ddev.site-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${DDEV_SITENAME}&lt;/span&gt;
      &lt;span class="na"&gt;com.ddev.approot&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${DDEV_APPROOT}&lt;/span&gt;
    &lt;span class="na"&gt;expose&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9000"&lt;/span&gt;
    &lt;span class="c1"&gt;# ports:&lt;/span&gt;
    &lt;span class="c1"&gt;#   - "9000:9000" # Cambia el puerto si es necesario&lt;/span&gt;
    &lt;span class="c1"&gt;# volumes:&lt;/span&gt;
    &lt;span class="c1"&gt;#   - "imaginary-data:/data" # Opcional, si necesitas almacenar datos persistentes&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-enable-url-source"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;VIRTUAL_HOST=$DDEV_HOSTNAME&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;HTTP_EXPOSE=7000:9000&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;HTTPS_EXPOSE=7001:9000&lt;/span&gt;

&lt;span class="c1"&gt;# volumes:&lt;/span&gt;
&lt;span class="c1"&gt;#   imaginary-data:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y ya tenemos el proxy espuestos, lo siguiente es que cuando servimos una imagen lo tenemos que hacer apuntando al proxy y no a Drupal.&lt;/p&gt;

</description>
      <category>images</category>
      <category>proxy</category>
      <category>ddev</category>
    </item>
    <item>
      <title>Añadir un enlace a un campo booleano</title>
      <dc:creator>oskar calvo</dc:creator>
      <pubDate>Fri, 12 Jun 2026 06:42:10 +0000</pubDate>
      <link>https://dev.to/oskar_calvo_1615a9b3b293f/anadir-un-enlace-a-un-campo-booleano-1ad</link>
      <guid>https://dev.to/oskar_calvo_1615a9b3b293f/anadir-un-enlace-a-un-campo-booleano-1ad</guid>
      <description>&lt;p&gt;Buenas, esto es algo que he tenido que sacar esta semana, algo que no esta resuelto en ningún módulo ni contribuido ni del core, no he hecho un módulo, únicamente un simple form_alter para resolverlo.&lt;/p&gt;

&lt;p&gt;Contexto: se ha creado un tipo de contenido (nodo) llamado incidencia para que los usuarios anónimos puedan dar de alta incidencias, se ha creado un nodo y no un webform porque esas incidencias tendrán estados, comentarios de las personas que tienen que resolverlas y supervisarlas, etc.&lt;/p&gt;

&lt;p&gt;Uno de los campos de este contenido es un cambo booleano tipo "Acepto condiciones y política de privacidad" y se quería añadir un enlace en la palabra "política de privacidad" para que llevase al nodo de política de privacidad.&lt;/p&gt;

&lt;p&gt;aquí el 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="cd"&gt;/**
   * Privacy policy link.
   */&lt;/span&gt;
  &lt;span class="na"&gt;#[Hook('form_alter')]&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;privacy_policy_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;$form&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;FormStateInterface&lt;/span&gt; &lt;span class="nv"&gt;$form_state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$form_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&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;$form_id&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="s1"&gt;'node_issue_form'&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="c1"&gt;// NOTE: Para una segunda iteración hacer un formulario para elegir cual es el nodo de politica de privacidad y desde el hook_form_alter llamarlo para no tener a fuego la ruta.&lt;/span&gt;

    &lt;span class="nv"&gt;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Url&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;fromUserInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/politica-privacidad'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$form&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'field_issue_privacy_policy'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'widget'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'value'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'#title_display'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'after'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$form&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'field_issue_privacy_policy'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'widget'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'value'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'#title'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s1"&gt;'Acepto la &amp;lt;a href=":url" target="_blank"&amp;gt;política de privacidad&amp;lt;/a&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;':url'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toString&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;



</description>
      <category>drupal</category>
      <category>drupal11</category>
      <category>bool</category>
      <category>link</category>
    </item>
    <item>
      <title>Reseña de la Commit Conf 2026</title>
      <dc:creator>oskar calvo</dc:creator>
      <pubDate>Sun, 07 Jun 2026 10:00:54 +0000</pubDate>
      <link>https://dev.to/oskar_calvo_1615a9b3b293f/resena-de-la-commit-conf-2026-4hpd</link>
      <guid>https://dev.to/oskar_calvo_1615a9b3b293f/resena-de-la-commit-conf-2026-4hpd</guid>
      <description>&lt;p&gt;Este fin de semana ha sido la Commit Conf 2026.&lt;/p&gt;

&lt;p&gt;Tengo que reconocer que me ha gustado, lo primero agradecer a la organización porque solo aquellos que han organizado eventos saben el trabajo que supone preparar algo así y que salga todo a la perfección; desde aquí mi más sincero reconocimiento y aplauso.&lt;/p&gt;

&lt;p&gt;El segundo agradecimiento a los voluntarios, sin ellos esto no habría sido posible, que alguien pare durante dos días su día a día para ayudar en un evento como este no es algo que se vea todos los días en este mundo donde está todo cada vez más acelerado, loco y cambiante.&lt;/p&gt;

&lt;p&gt;Para mi este año ha sido diferente, y no se si seré capaz de explicar lo que quiero decir o va a quedar todo mal o peor, pero vamos con al disertación.&lt;/p&gt;

&lt;p&gt;Después de la pandemia y con la aparición de los modelos de IA débil las comunidades virtuales y también foros han ido desapareciendo, ya no necesitamos ir a un foro, chats, o portales para hacer preguntas; tenemos una caja de texto o una consola en la que planteamos lo que queremos hacer se resuelve (mejor o peor, pero se resuelve), es por eso que este año me fijé más en los Open Space que había y decidí a cuál podría ir. De las charlas, van a estar todas grabadas, incluso si no es posible encontrar al ponente para plantearle dudas después de ver la charla también se puede plantear la duda a un modelo de IA débil para leer su respuesta.&lt;/p&gt;

&lt;p&gt;No quiero decir con esto que las charlas (yo dí una charla) no sean necesarias, sean cosa del pasado y a otra cosa mariposa; lo que quiero hacer entender que en este mundo donde vivimos ahora mismo que nos suele separar del resto del mundo una interface el track de Espacio Abierto tiene un valor añadido por el contacto humano con otras personas que también asisten y no es tanto una relación "Maestro - Alumno" como podría ser una charla, es verdad que las sesiones de los Espacios Abiertos van guiadas por las personas que los proponen pero esas personas no están subidas a una tarima como si ocurre en las conferencias.&lt;/p&gt;

&lt;p&gt;No se si he conseguido expresar correctamente o me fan a "funar" por esto.&lt;/p&gt;

&lt;p&gt;El viernes el primer Espacio Abierto al que asistí fue el que organizaron: Maria Isabel Martin and Osmani Redondo con el título "Buscar trabajo en tiempos de crisis (y de IA)". Tengo que aplaudir porque fue una dinámica muy entretenida, se buscaba que todo el mundo participase y estaba muy bien organizado, con los tiempos controlados, se paraba a debatir los conceptos que se consideraban interesantes pero sin llegar a estancarnos.&lt;/p&gt;

&lt;p&gt;El siguiente Espacio Abierto al que asistí fue el organizado por Daniel Rey Rey "De meetups a movimiento: construir comunidades técnicas que crecen", y en esta dinámica se volvió a tocar el tema que se lleva años tocando sobre que está ocurriendo con las comunidades de antes de la pandemia, si es posible re-impulsar/recuperar comunidades o si las comunidades que había hasta 2019 ya es cosa del pasado y tenemos que asumir que "el mundo ha cambiado". &lt;/p&gt;

&lt;p&gt;El sábado la dinámica de Ana G Amor "Encajar, fingir o callar: cómo sobrevivimos (y qué nos cuesta). Masking y síndrome del impostor en entornos Tech" que quizás fue la que más me gustó; en esta sesión los asistentes nos pudimos sincerar con nuestros miedos, y pudimos descubrir que no somos los únicos que a veces pensamos cosas raras.&lt;/p&gt;

&lt;p&gt;Luego pude asistir a media dinámica de Jorge Castaño ¿Qué implica ser Sénior?, la parte en la que estuve de la dinámica también muy bien organizada, con preguntas que iban al debate interesante y enriquecedor.&lt;/p&gt;

&lt;p&gt;Y conseguí llegar a la segunda parte de de la dinámica Tech Communities, como siempre un placer debatir con otras comunidades de lo que nos duele y nos preocupa y quizás de esta Espacio Abierto es la frase que dijo una de las personas que participaban "si una comunidad tiene que morir que muera".&lt;/p&gt;

&lt;p&gt;Además de las dinámicas de los Espacios Abiertos estuve en algunas charlas y conversando con la gente de la comunidad de SysAdmin de Galicia que hicieron acto de presencia.&lt;/p&gt;

&lt;p&gt;De todo lo que he hablado con muchas personas en esta sesión de la Commit Conf es que todos estamos con dudas, con miedos, con incertidumbre en nuestras vidas, algunos he vivido más años otros menos pero las dudas, miedos, e incertidumbres pare que nos acompañan a todos en esta época tan líquida donde tenemos que vivir y creo que para mi esta Commit Conf 2026 es poder encontrar una pequeña isla en un mundo oceánico.&lt;/p&gt;

</description>
      <category>commitconf</category>
      <category>commitconf2026</category>
    </item>
    <item>
      <title>Añadir aljibe a un proyecto que ya tiene Ddev</title>
      <dc:creator>oskar calvo</dc:creator>
      <pubDate>Tue, 14 Apr 2026 11:56:31 +0000</pubDate>
      <link>https://dev.to/oskar_calvo_1615a9b3b293f/anadir-aljibe-a-un-proyecto-que-ya-tiene-ddev-c6m</link>
      <guid>https://dev.to/oskar_calvo_1615a9b3b293f/anadir-aljibe-a-un-proyecto-que-ya-tiene-ddev-c6m</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/Metadrop/ddev-aljibe/" rel="noopener noreferrer"&gt;Ddev-aljibe&lt;/a&gt; es un plugin creado por la empresa &lt;a href="https://metadrop.net/es" rel="noopener noreferrer"&gt;Metadrop&lt;/a&gt; para añadir en una única instalación todas las dependencias necesarias para escribir y ejecutar test como la ejecutar herramientas de análisis de código estático de PHP que se pueden usar en proyectos de Drupal que se ejecutan mediante &lt;a href="https://ddev.com/" rel="noopener noreferrer"&gt;Ddev&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Vamos con los pasos para instalar e implementar Aljibe y comentar que cosas me he encontrado.&lt;/p&gt;

&lt;p&gt;Tengo que avisar que previamente en mi proyecto en Ddev ya había instalado y configurado &lt;a href="https://github.com/phpro/grumphp" rel="noopener noreferrer"&gt;GrumPHP&lt;/a&gt; junto a &lt;a href="https://github.com/squizlabs/PHP_CodeSniffer" rel="noopener noreferrer"&gt;PHPCS&lt;/a&gt;, &lt;a href="https://phpstan.org/" rel="noopener noreferrer"&gt;PHPStan&lt;/a&gt; y &lt;a href="https://phpmd.org/" rel="noopener noreferrer"&gt;PHPMD&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;El primer paso para instalar ddev-aljibe es añadir el plugin &lt;code&gt;ddev add-on get metadrop/ddev-aljibe&lt;/code&gt;, una vez añadido el plugin reiniciamos &lt;code&gt;ddev restart&lt;/code&gt; y ejecutamos &lt;code&gt;ddev aljibe-assistant&lt;/code&gt;, el asistente pasará a realizarnos una serie de preguntas para realizar la instalación.&lt;/p&gt;

&lt;p&gt;Una cosa que tuve que hacer a mitad del proceso y que se puede hacer previamente es actualizar el proyecto &lt;code&gt;ddev composer update&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Al ejecutar el asistente este irá haciendo una serie de preguntas, las respuesta que yo di para que no me sobrescribiera el proyecto son las siguientes:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Please enter the project name (default XXXX):&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;En esta opción simplemente pulsar intro.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;By default, Drupal 11 will be installed. Do you wnat to install Drupal 10 instead? [y/N]&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;En esta opción indicamos 10 u 11, en mi caso era un proyecto en Drupal 11 así que marqué N&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do you want to initialize a git repository for your new project? [Y/n]&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;En mi caso como ya tenía un proyecto de git montado señalé n.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do you want to install Drupal? [Y/n]&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Como el proyecto ya tenía Drupal instalado le dije que no.&lt;/p&gt;

&lt;p&gt;A continuación pasó a instalar todo.&lt;/p&gt;

&lt;p&gt;Un problema que sufrí es que en la instalación me movió el paquete  &lt;code&gt;vlucas/phpdotenv&lt;/code&gt; de requirement a requirement-dev en el archivo composer.json, no tengo claro si esto es porque yo tenía alguna configuración rara o es así como lo hace, pero lo volví a mover a requirement y todo volvió a funcionar.&lt;/p&gt;

&lt;p&gt;Al ejecutar &lt;code&gt;ddev behat&lt;/code&gt; me daba los siguientes errores.&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="no"&gt;PHP&lt;/span&gt; &lt;span class="nc"&gt;Fatal&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Declaration&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="nc"&gt;NuvoleWeb\Drupal\DrupalExtension\ServiceContainer\DrupalExtension&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition&lt;/span&gt; &lt;span class="nv"&gt;$builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;must&lt;/span&gt; &lt;span class="n"&gt;be&lt;/span&gt; &lt;span class="n"&gt;compatible&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;Drupal\DrupalExtension\ServiceContainer\DrupalExtension&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition&lt;/span&gt; &lt;span class="nv"&gt;$builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;void&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;www&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;vendor&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;nuvoleweb&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;drupal&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;behat&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nc"&gt;DrupalExtension&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nc"&gt;ServiceContainer&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nc"&gt;DrupalExtension&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;php&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="mi"&gt;55&lt;/span&gt;

&lt;span class="nc"&gt;Fatal&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Declaration&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="nc"&gt;NuvoleWeb\Drupal\DrupalExtension\ServiceContainer\DrupalExtension&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition&lt;/span&gt; &lt;span class="nv"&gt;$builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;must&lt;/span&gt; &lt;span class="n"&gt;be&lt;/span&gt; &lt;span class="n"&gt;compatible&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;Drupal\DrupalExtension\ServiceContainer\DrupalExtension&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition&lt;/span&gt; &lt;span class="nv"&gt;$builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;void&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;www&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;vendor&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;nuvoleweb&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;drupal&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;behat&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nc"&gt;DrupalExtension&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nc"&gt;ServiceContainer&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nc"&gt;DrupalExtension&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;php&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="mi"&gt;55&lt;/span&gt;
&lt;span class="nc"&gt;Failed&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="n"&gt;behat&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;exit&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El parche para solucionarlo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;--- a/src/DrupalExtension/ServiceContainer/DrupalExtension.php
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/src/DrupalExtension/ServiceContainer/DrupalExtension.php
&lt;/span&gt;&lt;span class="p"&gt;@@ -16,7 +16,7 @@&lt;/span&gt; class DrupalExtension extends BaseDrupalExtension {
   /**
    * {@inheritdoc}
    */
&lt;span class="gd"&gt;-  public function load(ContainerBuilder $container, array $config) {
&lt;/span&gt;&lt;span class="gi"&gt;+  public function load(ContainerBuilder $container, array $config): void {
&lt;/span&gt;     parent::load($container, $config);
&lt;span class="err"&gt;
&lt;/span&gt;     // Load default service definitions.
&lt;span class="p"&gt;@@ -52,7 +52,7 @@&lt;/span&gt; class DrupalExtension extends BaseDrupalExtension {
   /**
    * {@inheritdoc}
    */
&lt;span class="gd"&gt;-  public function configure(ArrayNodeDefinition $builder) {
&lt;/span&gt;&lt;span class="gi"&gt;+  public function configure(ArrayNodeDefinition $builder): void {
&lt;/span&gt;     parent::configure($builder);
&lt;span class="err"&gt;
&lt;/span&gt;     $builder-&amp;gt;append(
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;También encontré que se modificaron los archivos .gitignore, settings.php, grumphp.yml; no tengo claro si la modificación de .gitignore y settings.php es por la actualización a la ultima versión del core de Drupal o por la instalación del plugin, en el caso del archivo grumphp.yml si que es cosa de Aljibe, mi solución fue revisar lo que había cambiado, lo que me gustaba mantenerlo, lo que no me gustaba quitarlo y recuperar lo que yo tenía.&lt;/p&gt;

&lt;p&gt;Por ejemplo yo sigo Conventional Commits mientras que Metadrop propone commits que empiezan por "Issue #$number: ....". También revisé las configuraciones de PHPStan, PHPmd para ver si lo que había añadido en las configuraciones es lo mismo que yo tenía añadido.&lt;/p&gt;

</description>
      <category>drupal</category>
      <category>ddev</category>
      <category>testing</category>
    </item>
    <item>
      <title>Añadir un capo de tipo hora y minutos en un formulario</title>
      <dc:creator>oskar calvo</dc:creator>
      <pubDate>Wed, 01 Apr 2026 11:09:36 +0000</pubDate>
      <link>https://dev.to/oskar_calvo_1615a9b3b293f/anadir-un-capo-de-tipo-hora-y-minutos-en-un-formulario-kp7</link>
      <guid>https://dev.to/oskar_calvo_1615a9b3b293f/anadir-un-capo-de-tipo-hora-y-minutos-en-un-formulario-kp7</guid>
      <description>&lt;p&gt;Una de las cosas que más me gusta es el FORM API de Drupal.&lt;/p&gt;

&lt;p&gt;Hoy vamos a ver como añadir un campo para recoger únicamente horas y minutos, &lt;/p&gt;

&lt;p&gt;La forma de hacerlo es, a partir de la versión 11.30 de Drupal la siguiente.&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="nv"&gt;$form&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'datetime_time_only'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="s1"&gt;'#type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'datetime'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s1"&gt;'#date_date_element'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'none'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s1"&gt;'#date_time_element'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'time'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s1"&gt;'#date_time_format'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'H:i'&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;Si estamos trabajando en versiones anteriores a la 11.30 la forma que he encontrado de hacerlo es con el módulo duration_field el cual define un &lt;a href="https://git.drupalcode.org/project/duration_field/-/blob/8.x-2.2/src/Element/DurationElement.php" rel="noopener noreferrer"&gt;Element&lt;/a&gt; para que podamos añadir a nuestros formularios un campo de tipo time&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="c1"&gt;// Usage example:&lt;/span&gt;
 &lt;span class="c1"&gt;// @code&lt;/span&gt;
 &lt;span class="nv"&gt;$form&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'duration'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
   &lt;span class="s1"&gt;'#type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'duration'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s1"&gt;'#title'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Duration),
   // Only 15 minute increments.
   '&lt;/span&gt;&lt;span class="c1"&gt;#date_increment' =&amp;gt; 900,&lt;/span&gt;
   &lt;span class="c1"&gt;// Hide seconds.&lt;/span&gt;
   &lt;span class="s1"&gt;'#granularity'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'y:m:d:h:i'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="c1"&gt;// Require hours and minutes.&lt;/span&gt;
   &lt;span class="s1"&gt;'#required_elements'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'h:i'&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;Comento esto porque algunos modelos LLM no saben las diferentes opciones que existen y sugieren paparruchas.&lt;/p&gt;

</description>
      <category>drupal</category>
      <category>formapi</category>
      <category>time</category>
    </item>
    <item>
      <title>Drupal Canvas, creación de páginas I</title>
      <dc:creator>oskar calvo</dc:creator>
      <pubDate>Wed, 24 Dec 2025 13:17:49 +0000</pubDate>
      <link>https://dev.to/oskar_calvo_1615a9b3b293f/drupal-canvas-creacion-de-paginas-i-c3b</link>
      <guid>https://dev.to/oskar_calvo_1615a9b3b293f/drupal-canvas-creacion-de-paginas-i-c3b</guid>
      <description>&lt;p&gt;Vamos con la creación de una página, para ello simplemente le damos a crear una página en el botón &lt;code&gt;+ Add Page&lt;/code&gt; que nos llevará a una página como esta:&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%2Fj34fh1smxuu8f8tbzr03.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%2Fj34fh1smxuu8f8tbzr03.png" alt="Crear una página con Canvas y template Byte" width="800" height="369"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Importante&lt;/strong&gt;, en mi caso he añadido dos idiomas a la web, con la ruta &lt;code&gt;https://drupalcms.ddev.site/en/canvas/editor/canvas_page/12&lt;/code&gt; y &lt;code&gt;https://drupalcms.ddev.site/es/canvas/editor/canvas_page/12&lt;/code&gt; no me funciona Canvas (&lt;em&gt;tengo que investigar esto&lt;/em&gt;), pero con la ruta &lt;code&gt;https://drupalcms.ddev.site/canvas/editor/canvas_page/12&lt;/code&gt; vemos la página lista para crear contenido.&lt;/p&gt;

&lt;p&gt;Esta página recuerda en su estructura a la de Gutenberg de Wordpress&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%2Fykwcld9oqeg9grtoxliw.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%2Fykwcld9oqeg9grtoxliw.png" alt="Gutenberg editor" width="800" height="541"&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lateral izquierdo
&lt;/h2&gt;

&lt;p&gt;En el lateral izquierdo tenemos el menú principal, con los siguientes elementos en orden:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Library&lt;/li&gt;
&lt;li&gt;Layers&lt;/li&gt;
&lt;li&gt;Code&lt;/li&gt;
&lt;li&gt;Pages&lt;/li&gt;
&lt;li&gt;Templates&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Library
&lt;/h3&gt;

&lt;p&gt;Esta opción del menú nos pinta todos los componentes que tenemos habilitados* en nuestro portal web y también nos permite elegir entre patrones si tenemos creado alguno.&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%2F6a7s07hpdcc7qxidgoze.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%2F6a7s07hpdcc7qxidgoze.png" alt=" " width="392" height="831"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Layers
&lt;/h3&gt;

&lt;p&gt;Esta opción nos permite definir con que capa de la página queremos trabajar, en e caso de Byte existen tres capas, Contenido, cabecera, y pie.&lt;/p&gt;

&lt;p&gt;También muestra los diferentes componentes que se están usando en cada capa en una estructura jerárquica, y en permite mover algunos componentes arriba y abajo.&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%2Fpvizs7ojmvmvactlfjqa.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%2Fpvizs7ojmvmvactlfjqa.png" alt=" " width="800" height="421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Code
&lt;/h3&gt;

&lt;p&gt;Esta opción nos permite añadir componentes mediante código, a esto e dedicaremos una entrada en exclusiva de esta serie.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pages
&lt;/h3&gt;

&lt;p&gt;Esta opción nos muestra el listado de todas las páginas que existen, recordemos, páginas de Canvas.&lt;/p&gt;

&lt;h3&gt;
  
  
  Templates
&lt;/h3&gt;

&lt;p&gt;Esta opción nos permite usar Canvas para personalizar los view mode que tenemos definidos de  tipos de nodos.&lt;/p&gt;

&lt;p&gt;En caso de que se quiera usar un view mode concreto tenemos que ver si está activado en el nodo para poder usarlo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTA&lt;/strong&gt;: Lo que he visto por ahora con los nodos es que si añadimos un componente a un tipo de nodo (por ejemplo de blog) en Canvas afecta a todos los nodos, no he visto una forma para que un componente se aplique a un único nodo, estoy preguntando y mirando esto, porque si esto no se puede realizar sería algo a tener en cuenta si queremos elegir entre Layout Builder y Canvas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lateral derecho
&lt;/h2&gt;

&lt;p&gt;En el lateral derecho vemos las opciones de configuración del componente que se está trabando. Cuando se entra a la página lo que se muestra son los valores básicos de la página: título, URL, SEO Settings, Media, Page Title (meta-atributos), Meta Description.&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%2F2t1ine7liqqhtz7l3g64.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%2F2t1ine7liqqhtz7l3g64.png" alt="Page data" width="353" height="821"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>tooling</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Drupal Canvas, Adminstración de contenido</title>
      <dc:creator>oskar calvo</dc:creator>
      <pubDate>Wed, 24 Dec 2025 12:08:50 +0000</pubDate>
      <link>https://dev.to/oskar_calvo_1615a9b3b293f/canvas-parte-2-primeros-pasos-4gf4</link>
      <guid>https://dev.to/oskar_calvo_1615a9b3b293f/canvas-parte-2-primeros-pasos-4gf4</guid>
      <description>&lt;p&gt;Este artículo los escribo con una instalación de DrupalCMS beta 2.x y template de instalación "Byte".&lt;/p&gt;

&lt;p&gt;Cuando hemos arrancado lo primero que he visto es que las páginas de canvas son independientes de la sección de contenido que solemos ver en Drupal.&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%2Flsvbrnw82pdxh86ebq5w.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%2Flsvbrnw82pdxh86ebq5w.png" alt=" " width="313" height="245"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pages nos pinta el listado de "paginas" (no confundir con "basic pages") de Canvas, y el enlace CMS nos pinta la administración clasica de &lt;code&gt;/admin/content&lt;/code&gt; de Drupal.&lt;/p&gt;

&lt;h4&gt;
  
  
  Pages
&lt;/h4&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%2Ffew3z0wfmgbfceuiraj9.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%2Ffew3z0wfmgbfceuiraj9.png" alt="Pages" width="800" height="369"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Drupal CMS
&lt;/h4&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%2Fqnb3795xbm267av65t7x.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%2Fqnb3795xbm267av65t7x.png" alt="Drupal CMS" width="800" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Como se puede ver en las imágenes la administración (publicar/despublicar/borrar) para las páginas de Canvas es parecida, las operaciones que podemos hacer son borrar/ver/editar, y crear una página nueva además de buscar por el título de la página y filtrar por publicadas/despublicadas.&lt;/p&gt;

</description>
      <category>drupal</category>
      <category>drupalcms</category>
      <category>uidesign</category>
      <category>contentwriting</category>
    </item>
    <item>
      <title>Drupal Canvas, introducción</title>
      <dc:creator>oskar calvo</dc:creator>
      <pubDate>Wed, 24 Dec 2025 11:57:09 +0000</pubDate>
      <link>https://dev.to/oskar_calvo_1615a9b3b293f/canvas-parte-1-566b</link>
      <guid>https://dev.to/oskar_calvo_1615a9b3b293f/canvas-parte-1-566b</guid>
      <description>&lt;p&gt;&lt;a href="https://www.drupal.org/project/canvas" rel="noopener noreferrer"&gt;Canvas &lt;/a&gt;es un módulo añadido a Drupal que permite la construcción de páginas webs simplemente agarrando y soltando elementos y luego personalizando dichos elementos.&lt;/p&gt;

&lt;p&gt;Uno de los puntos importantes respecto a otros módulos de Drupal que permitían construir páginas es que esta vez Canvas no viene de desarrolladores de backend dando funcionalidades a creadores de contenido/diseñadores, sino que ha sido un trabajo de análisis de que había en el mercado, ver como funcionaba, coger lo mejor de todo aquello que se ha analizado, hablar con diseñadores y creadores de contenido y luego sacar las funcionalidades que se quieren implementar, vamos que se ha pensando más como un producto con los diseñadores y creadores de contenido como  público objetivo. &lt;/p&gt;

&lt;p&gt;Que nadie me mal interprete, no digo que Layout Builder, Paragraphs, Panels, etc... no fueran buenos productos, pero mi opinión personal es que en la comunidad de Drupal durante muchos años hemos pensado en sacar módulos/productos pensados para desarrolladores backend/hardcoder y no tanto herramientas sencillas de usar para diseñadores o creadores de contenidos, y esto es precisamente lo que hizo que Wordpress triunfase donde no lo hizo Drupal, ellos sacaron un producto pensado para diseñadores o personas sin conocimientos y sin ganas de leerse un libro enorme.&lt;/p&gt;

&lt;p&gt;Con Canvas Drupal rompe esa dinámica y tenemos una herramienta pensada para ese tipo de usuarios.&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%2Fi05u143qo880voyv6n93.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%2Fi05u143qo880voyv6n93.png" alt=" " width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>drupal</category>
      <category>uidesign</category>
      <category>ui</category>
      <category>contentwriting</category>
    </item>
    <item>
      <title>Añadir programáticamente un campo de tipo cshs a un formulario.</title>
      <dc:creator>oskar calvo</dc:creator>
      <pubDate>Tue, 11 Nov 2025 11:20:42 +0000</pubDate>
      <link>https://dev.to/oskar_calvo_1615a9b3b293f/anadir-programaticamente-un-campo-de-tipo-cshs-a-un-formulario-45o3</link>
      <guid>https://dev.to/oskar_calvo_1615a9b3b293f/anadir-programaticamente-un-campo-de-tipo-cshs-a-un-formulario-45o3</guid>
      <description>&lt;p&gt;El módulo &lt;a href="https://www.drupal.org/project/cshs" rel="noopener noreferrer"&gt;Client-side Hierarchical Select&lt;/a&gt; de Drupal permite elegir un elemento de un select cuando tenemos una jerarquía en árbol con múltiples valores.&lt;/p&gt;

&lt;blockquote&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%2Fojixqxc4epdtu300xnek.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%2Fojixqxc4epdtu300xnek.png" alt=" " width="640" height="150"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h6&gt;
  
  
  (Fuente de la foto: &lt;a href="https://www.drupal.org/project/cshs" rel="noopener noreferrer"&gt;https://www.drupal.org/project/cshs&lt;/a&gt;)
&lt;/h6&gt;
&lt;/blockquote&gt;

&lt;p&gt;Añadir este widget a las entidades de contenido es sencillo, simplemente en la pestaña de "Administrar la visualización del formulario" podemos elegir que widget queremos usar y si tenemos activado el módulo &lt;strong&gt;Cshs&lt;/strong&gt; lo veremos para los campos de taxonomías. &lt;/p&gt;

&lt;p&gt;El problema es que los formularios creados a medida para añadir funcionalidades, o los formularios de entidades de configuración no tienen la opción de "Administrar la visualización del formulario".&lt;/p&gt;

&lt;p&gt;En este caso muestro como usando el módulo Hux añado un hook_form_alter para añadir un campo adicional a la entidad que trae el módulo simplenews para poder categorizar los tipos de boletines que se crean usando un vocabulario ya definido en el proyecto.&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="cd"&gt;/**
   * Implement hook_form_alter().
   */&lt;/span&gt;
  &lt;span class="na"&gt;#[Alter('form')]&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;addThematicFieldToNewsletters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;$form&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;\Drupal\Core\Form\FormStateInterface&lt;/span&gt; &lt;span class="nv"&gt;$form_state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$form_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;void&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;in_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$form_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'simplenews_newsletter_edit_form'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'simplenews_newsletter_add_form'&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;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nv"&gt;$form&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'container_thematics'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'#type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'fieldset'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'#title'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Thematics'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="p"&gt;];&lt;/span&gt;

          &lt;span class="nv"&gt;$form&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'container_thematics'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'thematics'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'#type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'cshs'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'#title'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Select the thematic'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'#options'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;buildCshsOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'thematics'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'#weight'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="cd"&gt;/** @var \Drupal\simplenews\Entity\Newsletter $newsletter */&lt;/span&gt;
            &lt;span class="s1"&gt;'#default_value'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$form_state&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getFormObject&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getEntity&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getThirdPartySetting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'gobcan_newsletters'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'thematics'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

          &lt;span class="p"&gt;];&lt;/span&gt;

            &lt;span class="c1"&gt;// Añadir entity builder para procesar los third party settings&lt;/span&gt;
            &lt;span class="nv"&gt;$form&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'#entity_builders'&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'buildThematicsEntity'&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;Para definir correctamente el widget de formulario de Cshs tenemos que definir el campo del formulario como ´'#type' =&amp;gt; 'cshs',´ , además el array de ´#Options´ no sirve un array de tipo clave valor, sino que tiene que tener una estructura concreta para que Cshs pueda procesarlo, por eso se ha creado el método ´buildCshsOptions()´.&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="cd"&gt;/**
     * Entity builder para las temáticas.
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;buildThematicsEntity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$entity_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;\Drupal\simplenews\Entity\Newsletter&lt;/span&gt; &lt;span class="nv"&gt;$newsletter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;$form&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;\Drupal\Core\Form\FormStateInterface&lt;/span&gt; &lt;span class="nv"&gt;$form_state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nv"&gt;$thematics_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$form_state&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'thematics'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nv"&gt;$newsletter&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setThirdPartySetting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$module_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'thematics'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$thematics_value&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;Esta funciión es la función necesaria para guardar los valores en la entidad como configuraciones de terceros.&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="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;buildCshsOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$vocabulary&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nv"&gt;$term_options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="nv"&gt;$terms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;entityTypeManager&lt;/span&gt;
      &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getStorage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'taxonomy_term'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;loadTree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$vocabulary&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="kc"&gt;NULL&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;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$terms&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$term&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nv"&gt;$termId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$term&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;id&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;$term&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;target_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$term_options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$termId&lt;/span&gt;&lt;span class="p"&gt;]&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;CshsOption&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$term&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="nv"&gt;$term&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;target_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$term_options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$termId&lt;/span&gt;&lt;span class="p"&gt;]&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;CshsOption&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$term&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getName&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;return&lt;/span&gt; &lt;span class="nv"&gt;$term_options&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 método construimos la estructura del array que necesita Cshs para poder construir el widget correcto, usamos la clase CshsOption que trae el módulo para hacerlo.&lt;/p&gt;

&lt;p&gt;El código es de &lt;a href="https://www.drupal.org/u/dmudie" rel="noopener noreferrer"&gt;David Mudie (dmudie)&lt;/a&gt; lo saqué de esta &lt;a href="https://www.drupal.org/project/cshs/issues/3361342" rel="noopener noreferrer"&gt;Issue&lt;/a&gt; en Drupal.org&lt;/p&gt;

</description>
      <category>drupal</category>
      <category>php</category>
      <category>formapi</category>
    </item>
    <item>
      <title>PHP 8.5 nos trae closures en constantes de clases.</title>
      <dc:creator>oskar calvo</dc:creator>
      <pubDate>Tue, 23 Sep 2025 21:52:15 +0000</pubDate>
      <link>https://dev.to/oskar_calvo_1615a9b3b293f/php-85-nos-trae-closures-en-constantes-de-clases-22d3</link>
      <guid>https://dev.to/oskar_calvo_1615a9b3b293f/php-85-nos-trae-closures-en-constantes-de-clases-22d3</guid>
      <description>&lt;p&gt;La verdad es que esta nueva funcionalidad me deja frío, y es añadir una constante que puede ejecutar una función anónima no termina de convencerme.&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Example&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;VALIDATOR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$value&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="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$value&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;(fuente del código: &lt;a href="https://pixicstudio.medium.com/php-8-5-new-developer-features-f9617311c590" rel="noopener noreferrer"&gt;https://pixicstudio.medium.com/php-8-5-new-developer-features-f9617311c590&lt;/a&gt;) &lt;br&gt;
Para resolver eso mismo podemos crear un &lt;a href="https://www.kai-sassnowski.com/post/php-functors-1/" rel="noopener noreferrer"&gt;functor&lt;/a&gt; o podemos usar también un &lt;a href="https://www.php.net/manual/en/language.oop5.traits.php" rel="noopener noreferrer"&gt;trait&lt;/a&gt;, o incluso una clase con un método estático (pero mejor los traits).&lt;/p&gt;

&lt;p&gt;Un functor al ser una clase se puede inyectar sin problemas en otras clases y un trait está precisamente pensado para ser usado dentro de otras clases.&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="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;Functor&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;fmap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;callable&lt;/span&gt; &lt;span class="nv"&gt;$fn&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Functor&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Arr&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;Functor&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$items&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$items&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;fmap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;callable&lt;/span&gt; &lt;span class="nv"&gt;$fn&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Functor&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;static&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;array_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;items&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;(fuente del código: &lt;a href="https://www.kai-sassnowski.com/post/php-functors-1/" rel="noopener noreferrer"&gt;https://www.kai-sassnowski.com/post/php-functors-1/&lt;/a&gt;) &lt;/p&gt;

&lt;p&gt;Un functor o un servicio normal los plantearía en caso de que sea necesario conectar con la capa de Infraestructura (acceso a bbdd, a servicios externos de terceros, etc.), un trait por otro lado lo implementaría en caso de que correspondiere a la capa de Dominio para manipular datos pero es que no veo en que momento sería buena idea o interesante añadir una función anónima a una constante.&lt;/p&gt;

&lt;p&gt;Así que lo dicho, no veo en qué momento sería buena idea añadir una función anónima a una constante. &lt;/p&gt;

</description>
      <category>php</category>
      <category>closures</category>
      <category>functors</category>
      <category>trait</category>
    </item>
    <item>
      <title>¿Ha sido una buena DrupalCampSpain 2025?</title>
      <dc:creator>oskar calvo</dc:creator>
      <pubDate>Tue, 23 Sep 2025 06:31:26 +0000</pubDate>
      <link>https://dev.to/oskar_calvo_1615a9b3b293f/ha-sido-una-buena-drupalcampspain-2025-12op</link>
      <guid>https://dev.to/oskar_calvo_1615a9b3b293f/ha-sido-una-buena-drupalcampspain-2025-12op</guid>
      <description>&lt;h2&gt;
  
  
  &lt;strong&gt;Voy a dar mi opinión personal.&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Creo que es una pregunta con una respuesta difícil y que depende más de los puntos de vista.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Desde mi punto de vista como persona que ha asistido a varias charlas&lt;/strong&gt;, la verdad es que la Camp ha sido interesante, quizás para mi gusto demasiada IA, pero no podemos negar que ahora mismo la IA es lo que está tirando de una parte importante del carro de la innovación en el sector de la informática/tecnología tanto a nivel de desarrollo y mejoras como de inversión económica, pero mi sensación es que después del pico del hype que hemos sufrido con la IA estamos en estos momentos en un momento meseta y no tengo claro si de aquí volverá a subir o tendrá una caída.&lt;/p&gt;

&lt;p&gt;También he echado en falta charlas más transversales que no sea únicamente Drupal, o en las que Drupal esté presente pero como una tecnología más, charlas del tipo "CSS moderno", "Gestión de proyectos", "SEO en un mundo el que Google está matando la navegación por los resultados de búsqueda", "el futuro de la web abierta", etc...&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Desde mi punto de vista como ponente&lt;/strong&gt;, este año he tenido que dar dos talleres, uno en el Centro de Nuevas Tecnologías de Galicia y otro en la Camp, la verdad es que como siempre ha sido una experiencia enriquecedora ya que el hecho de preparar, revisar, volver a preparar los talleres se aprende mucho y ayuda a asentar conocimientos. El único pero que me pongo a mi mismo es en taller de ECA que a pesar de tener ejemplos muy sencillos del módulo quise enseñar algunas cosas algo más complejas que no terminaron de funcionar bien, ya tengo un posit para ver que ha pasado y poder explicarlo a los asistentes en una futura publicación.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Desde mi punto de vista como parte de la organización&lt;/strong&gt;, creo que la Camp ha ido rodada, ocurrieron pequeñas cosas pero todo se pudo subsanar sin problemas, si hasta acompañó el tiempo durante todo el evento. &lt;/p&gt;

&lt;p&gt;Mención especial se merece &lt;a href="https://www.linkedin.com/in/esmeraldalcastro/" rel="noopener noreferrer"&gt;Esmeralda&lt;/a&gt;, que se ha currado unos textos increíbles para ir publicando en Linkedin, este debería ser el nivel a seguir los próximos años, quizás nos tenemos que plantear que de cara al sprint final de las Camps (cuando más hincapié hacemos en RRSS) la AED debería contratar a una persona que trabaje en comunicación/RRSS para que nos ayude con los textos.&lt;/p&gt;

&lt;p&gt;Lo dicho, creo que ha salido una Camp muy redonda y para estar Galicia en "Finis Terrae" la comunidad ha respondido muy bien y hemos tengo una Camp con una gran afluencia de profesionales. &lt;/p&gt;

</description>
      <category>drupal</category>
      <category>drupalcamp</category>
    </item>
  </channel>
</rss>
