carloscarrascal.com - Desvaríos de un desarrollador. Sobre GNU / Linux, Debian y sobre todo Drupal. https://www.carloscarrascal.com/en es Crear bloques por código en Drupal 8 https://www.carloscarrascal.com/blog/crear-bloques-por-codigo-en-drupal-8 <article data-history-node-id="44" class="node node--type-blog-post node--view-mode-rss group-one-column ds-2col-stacked-fluid clearfix"> <div class="group-header"> <div class="field field--name-node-title field--type-ds field--label-hidden field--item"><h1> Crear bloques por código en Drupal 8 </h1> </div> <div class="field field--name-node-post-date field--type-ds field--label-hidden field--item">Viernes, Agosto 23, 2019 - 14:08</div> </div> <div class="group-left"> <div class="field field--name-field-tags field--type-entity-reference field--label-hidden field--items"> <div class="field--item"><a href="/tags/d8" hreflang="es">D8</a></div> <div class="field--item"><a href="/tags/drupal" hreflang="es">Drupal</a></div> </div> <div class="field field--name-body field--type-text-with-summary field--label-hidden field--item"><p>En Drupal los bloques son una de las principales herramientas de que disponemos para mostrar distintos componentes o contenidos, y colocarlos en la página en distintas posiciones. Drupal viene con una serie de bloques por defecto, y cada módulo puede definir sus propios bloques, así que una de las cosas básicas de trabajar con Drupal pasa por aprender a crear nuestros propios bloques. </p> <p>Empezaremos por lo básico y luego lo complicaremos un poco para tocar algunos conceptos más interesantes.</p> <p> </p> <h2>Creando un bloque sencillo</h2> <p>Lo primero que vamos a necesitar es crear un nuevo módulo (o utilizar uno que ya tengamos) e instalarlo, para tener un sitio donde meter nuestro código.</p> <p>En Drupal 8 todos los bloques son instancias de la clase <a href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Component%21Plugin%21PluginBase.php/class/PluginBase/8.2.x">Plugin</a>, y el gestor de bloques (<a href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Block%21BlockManager.php/class/BlockManager/8.2.x">Block manager</a>) se encargará de encontrar los bloques que hayamos definido por medio de anotaciones de PHP en nuestras clases.</p> <p>Primero crearemos la clase para nuestro bloque en un archivo dentro de nuestro módulo, que deberá estar en el directorio</p> <pre> src/Plugin/Block </pre> <p>El nombre del archivo debe coincidir con el nombre de la clase, o no funcionará. Por ejemplo, vamos a crear un bloque HolaMundo, por lo que crearemos el siguiente archivo:</p> <pre> src/Plugin/Block/HolaMundo.php</pre> <p>Con el siguiente código:</p> <pre> <code>&lt;?php namespace Drupal\hola_mundo\Plugin\Block; use Drupal\Core\Block\BlockBase; /** * Clase de control del bloque HolaMundo. * * @Block( * id = "hola_mundo", * admin_label = @Translation("Hola mundo"), * category = @Translation("Hola mundo"), * ) */ class HolaMundo extends BlockBase { /** * {@inheritdoc} */ public function build() { return [ '#markup' =&gt; $this-&gt;t('Hola mundo!'), ]; } }</code></pre> <p> </p> <p>Ahora debemos limpiar la caché, y ya podremos añadir nuestro nuevo bloque a una página desde el menú <em>Estructura &gt; Diseño de bloques</em>, y colocarlo en la región que queramos.</p> <p>Como veis, nuestra clase extenderá de <a href="https://api.drupal.org/api/drupal/core!lib!Drupal!Core!Block!BlockBase.php/class/BlockBase/8.2.x">BlockBase</a>, debemos poner las anotaciones de PHP (<em>id</em>, <em>admin_label</em> y <em>category</em>), y solamente tenemos que implementar el método <em>build()</em> que tiene que devolver un array para ser renderizado. Si utilizamos <em>#markup</em>, podemos devolver ahi todo el código HTML que queramos que pinte nuestro bloque.</p> <p> </p> <h2>Usando templates para nuestros bloques</h2> <p>Como en la mayoría de los casos lo que queremos pintar en el bloque no será algo tan sencillo como este ejemplo, es buena idea definir un template, de forma que podamos separar la lógica de la presentación, y evitemos tener que meter todo el HTML de esta forma dentro de <em>#markup</em>, que es bastante poco elegante, por decir algo suave.</p> <p>Configurar un template o plantilla para nuestro nuevo bloque es bastante sencillo. La forma mas simple no requiere prácticamente nada, y aunque he visto muchos ejemplos donde dicen que es necesario implementar un <em>hook_theme()</em>, esto no es estrictamente necesario.</p> <p>Primero vamos a hacer unas modificaciones en nuestra función <em>build()</em> del bloque que acabamos de crear para pasarle algunas variables al template, y evitar meter código HTML en nuestras clases PHP. Ahora en vez de devolver todo dentro de #markup, pasaremos distintas variables a las que asignamos el nombre que queramos, en este caso serán <em>title</em> y <em>description</em>, pero podemos llamarlas como queramos: </p> <pre> /** * {@inheritdoc} */ public function build() { return [ 'title' =&gt; t('Hola mundo!'), 'description' =&gt; t('Esto es un bloque que pinta un hola mundo'), ]; }</pre> <p>Listo, sigamos adelante. Solamente con lo que tenemos hasta ahora, Drupal asignará por defecto ciertos templates para aplicar a nuestro bloque. Si activamos el <em>theme debug</em>, podremos ver los nombres de fichero que Drupal buscará dentro del tema que tengamos activo, que para nuestro caso son estos:</p> <pre> &lt;!-- FILE NAME SUGGESTIONS: * block--holamundo.html.twig * block--hola-mundo.html.twig * block--mimodulo.html.twig x block.html.twig --&gt;</pre> <ul> </ul> <p>Drupal utilizará el template de bloque por defecto <em>block.html.twig </em>cuando ninguno de esos otros ficheros existe.</p> <p>Entonces solo tenemos que crear el archivo de template para el bloque dentro de nuestro tema, usando alguno de esto nombres para el fichero. Es buena idea crear un directorio dentro del tema llamado <em>templates/block</em>, para tenerlos mejor organizados:</p> <pre> themes/custom/nuestro_tema/templates/block/block--hola-mundo.html.twig</pre> <p>Y dentro de este template utilizaremos las variables que hemos definido en <em>build()</em> para mostrar la información:</p> <pre> {# /** * @file * Hola mundo block. */ #} &lt;div class="wrapper hola-mundo--block"&gt; &lt;h1&gt;{{ content.title }}&lt;/h1&gt; &lt;p&gt;{{ content.description }}&lt;/p&gt; &lt;/div&gt;</pre> <p>Importante aquí que nuestras variables deben prefijarse con <em>content.nombre_de_variable</em>, o no veremos ningún resultado.</p> <p>No queda más que borrar caches y probar nuestro nuevo bloque.</p> <p> </p> </div> </div> <div class="group-footer"> <section> <h2>Comments</h2> <article data-comment-user-id="0" id="comment-750" about="/en/comment/750" typeof="schema:Comment" class="js-comment"> <mark class="hidden" data-comment-timestamp="1619643445"></mark> <footer> <article typeof="schema:Person" about="/user/0"> </article> <p><span rel="schema:author">Subido por <span lang="" typeof="schema:Person" property="schema:name" datatype="">Dani (no verificado)</span> el Mar, 22/12/2020 - 22:36</span> <span property="schema:dateCreated" content="2020-12-22T21:36:31+00:00" class="hidden"></span> </p> <a href="/en/comment/750#comment-750" hreflang="en">Enlace permanente</a> </footer> <div> <h3 property="schema:name" datatype=""><a href="/en/comment/750#comment-750" class="permalink" rel="bookmark" hreflang="en">Veo que sigues dándole al…</a></h3> <div property="schema:text" class="field field--name-comment-body field--type-text-long field--label-hidden field--item"><p>Veo que sigues dándole al Debian. Me sonaba que te habías cerificado en RedHat o algo así.<br /> Comentario Certified by DAI™.<br /> :-P</p> </div> <drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=750&amp;1=default&amp;2=en&amp;3=" token="NMaJhQvLeeUrFHobAsAaKPOX3HxSCVSrpPvZMPqWPuw"></drupal-render-placeholder> </div> </article> <article data-comment-user-id="0" id="comment-751" about="/en/comment/751" typeof="schema:Comment" class="js-comment"> <mark class="hidden" data-comment-timestamp="1619643462"></mark> <footer> <article typeof="schema:Person" about="/user/0"> </article> <p><span rel="schema:author">Subido por <span lang="" typeof="schema:Person" property="schema:name" datatype="">Dani (no verificado)</span> el Mar, 22/12/2020 - 22:41</span> <span property="schema:dateCreated" content="2020-12-22T21:41:45+00:00" class="hidden"></span> </p> <a href="/en/comment/751#comment-751" hreflang="en">Enlace permanente</a> </footer> <div> <h3 property="schema:name" datatype=""><a href="/en/comment/751#comment-751" class="permalink" rel="bookmark" hreflang="en">Un saludo tron que se me ha…</a></h3> <div property="schema:text" class="field field--name-comment-body field--type-text-long field--label-hidden field--item"><p>Un saludo tron que se me ha olvidao en el otro mensaje!!</p> </div> <drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=751&amp;1=default&amp;2=en&amp;3=" token="nSVT-dvfxVzmLIsa1CORCVsuPkRWRbF0nsxQGktt41s"></drupal-render-placeholder> </div> </article> <h2>Añadir nuevo comentario</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=44&amp;2=comment&amp;3=comment" token="jpdZrMBRWUjAYN_ne_tmNTYNeAZl-k3ekseHN6k5s9s"></drupal-render-placeholder> </section> </div> </article> Fri, 23 Aug 2019 12:08:55 +0000 root 44 at https://www.carloscarrascal.com Instalar PHP 7.2 en Debian 9 para Drupal 8 https://www.carloscarrascal.com/blog/instalar-php-72-en-debian-9-para-drupal-8 <article data-history-node-id="43" class="node node--type-blog-post node--view-mode-rss group-one-column ds-2col-stacked-fluid clearfix"> <div class="group-header"> <div class="field field--name-node-title field--type-ds field--label-hidden field--item"><h1> Instalar PHP 7.2 en Debian 9 para Drupal 8 </h1> </div> <div class="field field--name-node-post-date field--type-ds field--label-hidden field--item">Domingo, Octubre 21, 2018 - 01:19</div> </div> <div class="group-left"> <div class="field field--name-field-tags field--type-entity-reference field--label-hidden field--items"> <div class="field--item"><a href="/tags/php" hreflang="es">PHP</a></div> <div class="field--item"><a href="/tags/debian" hreflang="es">Debian</a></div> <div class="field--item"><a href="/tags/drupal" hreflang="es">Drupal</a></div> </div> <div class="field field--name-body field--type-text-with-summary field--label-hidden field--item"><p>Debian 9 trae por defecto en sus repositorios PHP 7.0.32. Desde Drupal 8.5.0 ya se soporta PHP 7.2, y por si no os habeis dado cuenta, <strong>Drupal 8 dejará de soportar PHP 5.5 y 5.6 el día 6 de marzo de 2019</strong>, así que ya va siendo hora de actualizar.</p> <p>Subir a 7.2 es bastante sencillo:</p> <pre> sudo apt install apt-transport-https lsb-release ca-certificates sudo wget -O /etc/apt/trusted.gpg.d/php.gpg https://packages.sury.org/php/apt.gpg sudo sh -c 'echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" &gt; /etc/apt/sources.list.d/php.list' sudo apt update</pre> <p>Una vez actualizado, podemos comprobar la versión instalada con:</p> <pre> php -v</pre> <p>Si no se ha actualizado automáticamente, podemos actualizar php a mano:</p> <pre> sudo apt install php7.2 php7.2-cli php7.2-common php7.2-curl php7.2-gd php7.2-json php7.2-mbstring php7.2-mysql php7.2-opcache </pre> <p>Una vez actualizado PHP, hay que actualizar el módulo que usamos en el servidor web.  Para Apache:</p> <pre> sudo a2enmod php7.2 sudo a2dismod php7.1</pre> <p>Y reiniciar:</p> <pre> sudo service apache2 restart</pre> <p>Si estáis utilizando composer, es una buena idea incluir la versión de PHP a utilizar:</p> <pre> composer config platform.php 7.2</pre> <p>Y actualizar todo:</p> <pre> composer update</pre> <p>En algunos casos es buena idea borrar el fichero <em>composer.lock</em> antes de correr el update.</p> <p> </p> </div> </div> <div class="group-footer"> <section> <h2>Añadir nuevo comentario</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=43&amp;2=comment&amp;3=comment" token="k8gK-xVcL0gzDcnyrt0dwH140sYIcDugBHd2dQTEqg4"></drupal-render-placeholder> </section> </div> </article> Sat, 20 Oct 2018 23:19:02 +0000 root 43 at https://www.carloscarrascal.com Importando metatags con Drupal 8 y Migrate https://www.carloscarrascal.com/blog/importando-metatags-con-drupal-8-y-migrate <article data-history-node-id="41" class="node node--type-blog-post node--view-mode-rss group-one-column ds-2col-stacked-fluid clearfix"> <div class="group-header"> <div class="field field--name-node-title field--type-ds field--label-hidden field--item"><h1> Importando metatags con Drupal 8 y Migrate </h1> </div> <div class="field field--name-node-post-date field--type-ds field--label-hidden field--item">Viernes, Octubre 12, 2018 - 20:11</div> </div> <div class="group-left"> <div class="field field--name-field-tags field--type-entity-reference field--label-hidden field--items"> <div class="field--item"><a href="/tags/d8" hreflang="es">D8</a></div> <div class="field--item"><a href="/tags/migrate" hreflang="es">Migrate</a></div> <div class="field--item"><a href="/tags/drupal" hreflang="es">Drupal</a></div> </div> <div class="field field--name-body field--type-text-with-summary field--label-hidden field--item"><p>Estoy en mitad de un proyecto en el que estamos importando a Drupal 8 contenidos que vienen de un repositorio central, y se consumen usando una API REST en formato JSON.</p> <p>Hasta ahora esta funcionando bastante bien y sin muchos dolores de cabeza, pero tratando de importar los metadatos que vienen con nuestros contenidos a los campos que trae el módulo <a href="https://www.drupal.org/project/metatag">Metatag</a> he tenido que pegarme un poco hasta dar con la solución.</p> <p>Actualmente (octubre de 2017), el módulo Metatag no tiene soporte para Migrate en forma de plugins. Hay <a href="https://www.drupal.org/node/2563649">un ticket abierto</a> desde hace tiempo, con varios parches disponibles, pero aún no está completo. Lo bueno es que en ese mismo hilo hay un par de personas que cuentan como lo han hecho sin necesitar el parche, que era justo lo que yo intentaba, pero se me estaba escapando un detalle: serializar.</p> <p>Para esta importación estamos usando los siguientes módulos:</p> <ul> <li><a href="https://www.drupal.org/project/migrate">migrate</a>: en el core de Drupal</li> <li><a href="https://www.drupal.org/project/migrate_tools">migrate_tools</a>: en contrib</li> <li><a href="https://www.drupal.org/project/migrate_plus">migrate_plus</a>: en contrib</li> </ul> <p>Para importar contenido usando <em>migrate_plus</em> vamos a necesitar una serie de componentes:</p> <ul> <li>Un archivo YML de control para la migración, donde especificaremos el tipo de proceso, los campos entrada y salida</li> <li>Un plugin de importación de contenido, que será una clase que extienda de <a href="https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Plugin%21migrate%21source%21SourcePluginBase.php/class/SourcePluginBase/8.6.x">SourcePluginBase</a>, y donde podremos hacer algunas transformaciones.</li> <li>Lo normal será que hayamos creado un módulo custom para controlar toda nuestra importación.</li> </ul> <p>No me quiero meter mucho en detalle de cómo se hace la importación usando migrate_plus, porque quiero escribir un artículo entero para ello, así que me voy a centrar en importar los metadatos.</p> <p>Primero el archivo YML que controla la importación en mi módulo, migrate_plus.migration.json_page.yml:</p> <pre> id: json_page label: 'JSON migration for content type: Page' migration_group: my_group source: plugin: json_page cache_counts: true high_water_property: name: changed destination: plugin: entity:node bundle: page process: uuid: uuid langcode: language type: plugin: default_value default_value: page title: title created: changed changed: changed 'body/format': 'body/format' 'body/value': 'body/value' 'body/summary': 'body/summary' 'field_page_teaser/format': 'teaser/format' 'field_page_teaser/value': 'teaser/value' 'path/alias': alias 'promote/value': plugin: default_value default_value: 0 field_meta_tags: meta_tags # use forced module dependency so uninstall/reinstall works properly dependencies: enforced: module: - my_module_name</pre> <p>Lo pongo entero por si ayuda para ver como estoy importando otros campos, pero lo importante es esta línea para los metadatos:</p> <pre> field_meta_tags: meta_tags </pre> <p>Esta línea va a intentar importar los metadatos desde un campo 'meta_tags'  en el origen (el <em>source</em>), a un campo llamado <em>field_meta_tags</em>, que ya hemos creado en nuestro tipo de contenido, en este caso <em>page</em>, y que es de tipo Meta tags.</p> <p>En mi caso, los metadatos vienen en JSON que representa un nodo en este formato. Atención al detalle de que es '<em>metatags</em>', no '<em>meta_tags</em>':</p> <pre> "metatags":{ "title":"a simple node title | [site:name]", "og:title":"a simple node title", "twitter:title":"a simple node title", "description":"a simple description", "og:description":"a simple description", "og:updated_time":"2018-08-27T08:40:34-04:00", "article:published_time":"2017-04-13T04:26:00-04:00", "article:modified_time":"2018-08-27T08:40:34-04:00", "twitter:description":"a simple description", "canonical":"[site:url][current-page:url:path]", "og:site_name":"[site:name]", "og:type":"article", "og:url":"[site:url][current-page:url:path]", "twitter:card":"summary", "twitter:url":"[site:url][current-page:url:path]" },</pre> <p>Como veis ya vienen con tokens que Drupal va a entender, asi que genial. Lo que vamos a hacer es utilizar el plugin que hemos definido para esta importación (<em>json_page</em>) para coger estos datos que vienen en el source de la importación como '<em>metatags</em>', darles el formato adecuado, y colocarlos en '<em>meta_tags</em>', que es lo que espera el proceso de migración, definido en el archivo YML de antes.</p> <p>Este es el código de mi plugin:</p> <pre> class MyJsonPlugin extends SourcePluginBase { public function prepareRow(Row $row) { // Set URL alias. $alias = $row-&gt;getSourceProperty('alias'); if (!empty($alias)) { $row-&gt;setSourceProperty('alias', '/' . $alias); } // Set meta tags. $row-&gt;setSourceProperty('meta_tags', $row-&gt;getSourceProperty('metatags')); return parent::prepareRow($row); } protected function processMetaTags($data) { $metatags = []; if ($data) { foreach ($data as $key =&gt; $value) { $transforms = [ 'twitter:card' =&gt; 'twitter_cards_type', 'twitter:url' =&gt; 'twitter_cards_page_url', 'twitter:description' =&gt; 'twitter_cards_description', 'twitter:site' =&gt; 'twitter_cards_site', 'twitter:site:id' =&gt; 'twitter_cards_site_id', 'twitter:creator' =&gt; 'twitter_cards_creator', 'twitter:creator:id' =&gt; 'twitter_cards_creator_id', 'twitter:title' =&gt; 'twitter_cards_title', 'canonical' =&gt; 'canonical_url', 'og:url' =&gt; 'og_url', 'og:title' =&gt; 'og_title', 'og:site_name' =&gt; 'og_site_name', 'og:type' =&gt; 'og_type', 'og:description' =&gt; 'og_description', 'og:image' =&gt; 'og_image', 'og:image:url' =&gt; 'og_image_url', 'og:image:secure_url' =&gt; 'og_image_secure_url', 'og:image:type' =&gt; 'og_image_type', 'article:published_time' =&gt; 'article_published_time', 'article:modified_time' =&gt; 'article_modified_time', ]; if (array_key_exists($key, $transforms)) { $key = $transforms[$key]; } if ($value) { $metatags[$key] = $value; } } } if (!$metatags) { return NULL; } return serialize($metatags); } ... } </pre> <p>Básicamente hay dos funciones importantes: en <em>prepareRow</em>, cogemos los metadatos desde '<em>metatags</em>' que tiene el JSON, los formateamos y los volvemos a dejar bien bonitos en '<em>meta_tags</em>', y en la función <em>processMetaTags</em> los procesamos y serializamos.</p> <p>Aquí hay dos cosas a tener en cuenta:</p> <ul> <li>Mis datos de origen en el JSON, vienen con los nombres de campo de Drupal 7, por ejemplo <em>twitter:card</em>, y los nombres de los campos de Drupal 8 son diferentes, por eso en la función <em>processMetaTags</em> revisamos las claves las cambiamos por las que corresponden. En esta lista no están todas las posibles pero sí las más importantes. Intentaré actualizarla en el futuro cuando la tenga completa.</li> <li>Una vez construido el array con los meta datos, tenemos que <strong>serializarlo</strong> (función <a href="http://php.net/manual/en/function.serialize.php">serialize</a> de PHP) antes de llamar a <em>setSourceProperty</em>, porque si no lo hacemos, los datos no se guardarán correctamente.</li> </ul> <p>Y esto es todo amigos, si teneis alguna idea mejor o algún problema podéis dejar una nota en los comentarios.</p> <p> </p> <h3>Referencias</h3> <ul> <li>El artículo original con el que empece a jugar con Migrate: <a href="https://www.metaltoad.com/blog/drupal-8-migrating-data-json-files">https://www.metaltoad.com/blog/drupal-8-migrating-data-json-files</a></li> <li>El ticket del modulo Metatags para incluir los plugins de Migrate: <a href="https://www.drupal.org/node/2563649">https://www.drupal.org/node/2563649</a></li> </ul> </div> </div> <div class="group-footer"> <section> <h2>Comments</h2> <article data-comment-user-id="0" id="comment-38" about="/en/comment/38" typeof="schema:Comment" class="js-comment"> <mark class="hidden" data-comment-timestamp="1565734782"></mark> <footer> <article typeof="schema:Person" about="/user/0"> </article> <p><span rel="schema:author">Subido por <span lang="" typeof="schema:Person" property="schema:name" datatype="">Salvador B. (no verificado)</span> el Vie, 26/10/2018 - 14:49</span> <span property="schema:dateCreated" content="2018-10-26T12:49:17+00:00" class="hidden"></span> </p> <a href="/en/comment/38#comment-38" hreflang="en">Enlace permanente</a> </footer> <div> <h3 property="schema:name" datatype=""><a href="/en/comment/38#comment-38" class="permalink" rel="bookmark" hreflang="en">Migración Meta tags</a></h3> <div property="schema:text" class="field field--name-comment-body field--type-text-long field--label-hidden field--item"><p>Hola Carlos, te quería agradecer tu articulo. </p> <p>Te he encontrado a partir del ticket de Metatags (<a href="https://www.drupal.org/node/2563649">https://www.drupal.org/node/2563649</a>). Te has ganado un follower! :-D</p> <p>Saludos!</p> </div> <drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=38&amp;1=default&amp;2=en&amp;3=" token="Kp71kBs1el410NhBpfenh3PMri37GPQSw9Csso9QNSI"></drupal-render-placeholder> </div> </article> <h2>Añadir nuevo comentario</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=41&amp;2=comment&amp;3=comment" token="gL6owGXCmF4P6iAHBLDYmzBZgqth1K2tPxIOuqJ0FrQ"></drupal-render-placeholder> </section> </div> </article> Fri, 12 Oct 2018 18:11:26 +0000 root 41 at https://www.carloscarrascal.com Crear un usuario y asignar permisos en MySQL https://www.carloscarrascal.com/blog/crear-un-usuario-y-asignar-permisos-en-mysql <article data-history-node-id="40" class="node node--type-blog-post node--view-mode-rss group-one-column ds-2col-stacked-fluid clearfix"> <div class="group-header"> <div class="field field--name-node-title field--type-ds field--label-hidden field--item"><h1> Crear un usuario y asignar permisos en MySQL </h1> </div> <div class="field field--name-node-post-date field--type-ds field--label-hidden field--item">Jueves, Septiembre 6, 2018 - 19:20</div> </div> <div class="group-left"> <div class="field field--name-field-tags field--type-entity-reference field--label-hidden field--items"> <div class="field--item"><a href="/tags/mysql" hreflang="es">MySQL</a></div> <div class="field--item"><a href="/tags/database" hreflang="es">Database</a></div> </div> <div class="field field--name-body field--type-text-with-summary field--label-hidden field--item"><p>Normalmente para configurar el acceso de una aplicación web a la base de datos crearemos una cuenta de servicio para esa aplicación, y le daremos permisos según necesitemos, pero solamente a esa base de datos.</p> <p>Esta es una de esas cosas que uno hace mil veces pero nunca se acuerda de la sintaxis correcta. Aqui lo teneis:</p> <pre> CREATE USER 'nombre_de_usuario'@'localhost' IDENTIFIED BY 'vuestra_password'; GRANT ALL PRIVILEGES ON nombre_de_base_de_datos.* TO 'nombre_de_usuario'@'localhost'; FLUSH PRIVILEGES;</pre> <p>Si queremos asignarle todos los permisos para todas las bases de datos, el GRANT sería así:</p> <pre> GRANT ALL PRIVILEGES ON *.* TO 'nombre_de_usuario'@'localhost'; </pre> <p>Normalmente, antes habremos creado la base de datos a mano, o importando los datos de algún archivo. Crear la base de datos en MySQL puede ser tan sencillo como:</p> <pre> CREATE DATABASE nombre_de_base_de_datos;</pre> <p>Suele ser buena idea probar que ha funcionado correctamente. La mejor forma es probar el acceso usando el terminal:</p> <pre> mysql --user=nombre_de_usuario --password=vuestra_password nombre_de_base_de_datos</pre> <p>Si todo está correcto, nos abrirá una sesión de MySQL en el terminal. Si no conecta, es que hemos hecho mal alguno de los pasos anteriores.</p> </div> </div> <div class="group-footer"> <section> <h2>Añadir nuevo comentario</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=40&amp;2=comment&amp;3=comment" token="uf1jhokftQ9c-TrhcJdrBuejFK31TbOt8fXOfQFLDI8"></drupal-render-placeholder> </section> </div> </article> Thu, 06 Sep 2018 17:20:10 +0000 root 40 at https://www.carloscarrascal.com Revertir a una version anterior un paquete en Debian https://www.carloscarrascal.com/blog/revertir-una-version-anterior-un-paquete-en-debian <article data-history-node-id="39" class="node node--type-blog-post node--view-mode-rss group-one-column ds-2col-stacked-fluid clearfix"> <div class="group-header"> <div class="field field--name-node-title field--type-ds field--label-hidden field--item"><h1> Revertir a una version anterior un paquete en Debian </h1> </div> <div class="field field--name-node-post-date field--type-ds field--label-hidden field--item">Sábado, Julio 14, 2018 - 22:10</div> </div> <div class="group-left"> <div class="field field--name-field-tags field--type-entity-reference field--label-hidden field--items"> <div class="field--item"><a href="/tags/debian" hreflang="es">Debian</a></div> <div class="field--item"><a href="/tags/linux" hreflang="es">Linux</a></div> </div> <div class="field field--name-body field--type-text-with-summary field--label-hidden field--item"><p>Una de las diversiones de utilizar la versión <a href="https://www.debian.org/releases/sid/">Sid</a> (inestable) de Debian, es actualizar paquetes. Cualquier cosa puede pasar.</p> <p>Hoy tenia un rato libre y le he dado al apt upgrade a ver que pasaba. Lo que ha pasado es que VirtualBox ha dejado de funcionar, y no arranca las máquinas virtuales, con este error:</p> <ul> <li><a href="https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=902897">virtualbox: fails to start vm (VERR_LDRELF_RELOCATION_NOT_SUPPORTED)</a><br />  </li> </ul> <p>Después de revisar el hilo del error, parece que lo mas rápido es bajar de versión el paquete, ya que la última instalada es la <em>5.2.14-dfsg-4</em>, por lo que parece, la versión anterior (<em>5.2.14-dfsg-3</em> en este caso) parece que funciona bien.</p> <p>Así que voy a intertar explicaros como he hecho para bajar el paquete de versión, sin tener que bajar e instalar a mano el paquete, sino usando los repos de Debian.</p> <p>Primero, vamos a <a href="http://snapshot.debian.org/">snapshot.debian.org</a> y buscamos el paquete que nos da problemas, en mi caso virtualbox. Podemos usar la caja de búsqueda o rebuscar a mano por el índice, lo que os de mas morbo.</p> <p>Hay que seleccionar la versión que buscamos, y debajo del nombre del paquete vereis algo como esto:</p> <blockquote> <p>Seen in debian on 2018-07-12 14:59:27 in <a href="http://snapshot.debian.org/archive/debian/20180712T145927Z/pool/contrib/v/virtualbox/">/pool/contrib/v/virtualbox</a>. </p> </blockquote> <p>Al pinchar en el enlace, llegamos a una dirección de este tipo:</p> <ul> <li><a href="http://snapshot.debian.org/archive/debian/20180712T211510Z/pool/contrib/v/virtualbox/">http://snapshot.debian.org/archive/debian/20180712T211510Z/pool/contrib…</a></li> </ul> <p>Una vez encontrada esta URL, vamos a usarla configurando una nueva fuente para Apt:</p> <pre> cd /etc/apt/sources.list.d</pre> <p>Y creamos un nuevo archivo de fuente:</p> <pre> sudo vi virtualbox.list</pre> <p>Tenemos que usar parte de la URL de antes, sin poner la parte final. En mi caso añado la versión unstable, y el <em>contrib</em>, porque el paquete es de <em>contrib</em>. Quedaría algo así:</p> <pre> deb https://snapshot.debian.org/archive/debian/20180712T211510Z/ unstable contrib </pre> <p>Guardamos el archivo y actualizamos el <em>Apt</em> para que incluya el nuevo índice:</p> <pre> apt update</pre> <p>Una vez termine de actualizar, podemos sacar la lista de versiones disponibles del paquete que necesitamos:</p> <pre> apt-cache showpkg virtualbox</pre> <p>E instalar la que necesitemos, usando el nombre de la version en el mismo comando de <em>Apt</em>:</p> <pre> apt install virtualbox=5.2.14-dfsg-3</pre> <p>En mi caso, el paquete <em>virtualbox</em> tenía un paquete recomendado llamado <em>virtualbox-qt</em>, que <em>Apt</em> muy amablemente me indicó al tratar de instalarlo, así que decidí bajarlo de versión también por si acaso:</p> <pre> apt install virtualbox=5.2.14-dfsg-3 virtualbox-qt=5.2.14-dfsg-3 </pre> <p>Con esto se actualizaron los paquetes necesarios para esas versiones, y encima tuve la suerte de que VirtualBox volvió a funcionar perfectamente. Ahora veremos que pasa cuando salga la version siguiente (<em>5.2.14-dfsg-3</em>) y quiera actualizarlo... </p> <p>Ya os contaré.</p> </div> </div> <div class="group-footer"> <section> <h2>Añadir nuevo comentario</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=39&amp;2=comment&amp;3=comment" token="LlETczWEdD9n00RfEBpV3q0sbSk9m84l5hhCSQ9IHxY"></drupal-render-placeholder> </section> </div> </article> Sat, 14 Jul 2018 20:10:45 +0000 root 39 at https://www.carloscarrascal.com Acceso a base datos en Drupal 7. Parte 2 https://www.carloscarrascal.com/blog/acceso-base-datos-en-drupal-7-parte-2 <article data-history-node-id="37" class="node node--type-blog-post node--view-mode-rss group-one-column ds-2col-stacked-fluid clearfix"> <div class="group-header"> <div class="field field--name-node-title field--type-ds field--label-hidden field--item"><h1> Acceso a base datos en Drupal 7. Parte 2 </h1> </div> <div class="field field--name-node-post-date field--type-ds field--label-hidden field--item">Viernes, Noviembre 17, 2017 - 22:26</div> </div> <div class="group-left"> <div class="field field--name-field-tags field--type-entity-reference field--label-hidden field--items"> <div class="field--item"><a href="/tags/drupal" hreflang="es">Drupal</a></div> <div class="field--item"><a href="/tags/d7" hreflang="es">D7</a></div> <div class="field--item"><a href="/tags/database" hreflang="es">Database</a></div> <div class="field--item"><a href="/tags/issues" hreflang="es">Issues</a></div> </div> <div class="field field--name-body field--type-text-with-summary field--label-hidden field--item"><p>Continuamos con el ejemplo de acceso a base de datos en Drupal 7 que comenzamos hace unos días. Puedes consultar <a href="/blog/acceso-base-datos-en-drupal-7">la primera parte del artículo</a> para refrescar o si simplemente te la perdiste.</p> <p>En esta segunda parte vamos a ver cómo:</p> <ul> <li>Crear un formulario para que el usuario pueda crear un nuevo registro en nuestra tabla de <em>issues</em>.</li> <li>Usar el mismo formulario para modificar los datos de una incidencia.</li> <li>Eliminar el registro de una incidencia de la tabla.</li> </ul> <p> </p> <h2>Definir Hook menus para las acciones</h2> <p>Lo primero que vamos a hacer es ampliar nuestra implementación de <em>hook_menu()</em> para declarar las tres URLs nuevas que necesitamos:</p> <ul> <li>Una URL para modificar los datos de una incidencia: <em>/issues/edit/[issue_id]</em></li> <li>Otra para eliminar incidencias de la tabla: <em>/issues/delete/[issue_id]</em> </li> <li>Por último, para dar de alta incidencias desde un formulario de Drupal:  <em>/issues/create</em></li> </ul> <p>Atención, porque las dos primeras van a necesitar un parámetro que será el identificador (<em>issue_id</em>) de la incidencia que queremos modificar o eliminar, y que debemos especificar en el <em>hook_menu()</em>, que quedaría así (ojo a las partes en negrita):</p> <pre> /** * Implements hook_menu(). * * Defines URLS for the list, create, edit and delete actions. */ function issue_control_menu() { $items = array(); $items['issues/list'] = array( 'title' =&gt; 'Issues List', 'description' =&gt; 'Issues List from the database.', 'page callback' =&gt; 'issue_control_list', 'page arguments' =&gt; array(), 'access callback' =&gt; TRUE, ); $items['issues/create'] = array( 'title' =&gt; 'Issues Form', 'description' =&gt; 'A form to write rows into issues table.', 'page callback' =&gt; 'drupal_get_form', 'page arguments' =&gt; array('issue_control_form'), 'access callback' =&gt; TRUE, ); $items['<strong>issues/edit/%</strong>'] = array( 'title' =&gt; 'Issues Form', 'description' =&gt; 'A form to write rows into issues table.', 'page callback' =&gt; 'drupal_get_form', '<strong>page arguments</strong>' =&gt; <strong>array('issue_control_form', 2)</strong>, 'access callback' =&gt; TRUE, ); $items['<strong>issues/delete/%</strong>'] = array( 'title' =&gt; 'Delete issue', 'description' =&gt; 'Deletes an issue.', 'page callback' =&gt; 'issue_control_perform_delete', '<strong>page arguments</strong>' =&gt; <strong>array(2)</strong>, 'access callback' =&gt; TRUE, ); return $items; } </pre> <p>Las definiciones de las URLs de modificar y eliminar llevan un símbolo <em><strong>%</strong></em> que actua de comodín, y se pasa después como parámetro a la función que definimos en <em>'page callback'</em>, mediante la siguiente línea de <em>'page arguments'</em>. En nuestro caso estamos pasando el argumento <strong>2</strong>, ya que el 0 sería 'issues', el 1 sería 'delete' o 'edit', y el 2 será el identificador de la incidencia que ya estamos incluyendo en la URL cuando creamos nuestra lista.</p> <p>De esta forma, cuando llamamos a la url <em><a href="http://nuestro.drupal/issue/edit/1">http://nuestro.drupal/issue/edit/1</a></em>, el último 1 (que es el <em>issue_id</em>) se recibirá en nuestra función como un parámetro.</p> <p>Además, en el caso de las llamadas de modificar y crear, la función que ponemos en el '<em>page callback</em>' no es directamente una función declarada por nosotros, sino una llamada a <a href="https://api.drupal.org/api/drupal/includes%21form.inc/function/drupal_get_form/7.x">drupal_get_form()</a>, a la que pasamos el nombre de nuestra función que pintará realmente el formulario. </p> <p> </p> <h2>Acceso a datos: Las funciones básicas</h2> <p>Antes de empezar a desarrollar el formulario para editar y crear incidencias, vamos a empezar por la parte básica primero: las funciones de acceso a datos. Es lógico que implementemos estas funciones esenciales, ya que lo normal en aplicaciones grandes es que tengamos varias llamadas, por ejemplo, para cargar una incidencia en varias partes del código, por lo podremos reutilizalas, y favorecemos la encapsulación, el principio de responsabilidad simple, etc. </p> <p>Estas son las funciones que vamos a necesitar:</p> <table class="table table-striped"> <tbody> <tr> <th>Acción</th> <th>Función</th> <th>Descripción</th> </tr> <tr> <td>Cargar</td> <td>issue_control_load_issue()</td> <td>Devuelve una incidencia.</td> </tr> <tr> <td>Nueva</td> <td>issue_control_add()</td> <td>Crea una indicencia en la tabla.</td> </tr> <tr> <td>Modificar</td> <td>issue_control_edit()</td> <td>Modifica los datos de una incidencia.</td> </tr> <tr> <td>Eliminar</td> <td>issue_control_delete()</td> <td>Eliminar una indicencia.</td> </tr> </tbody> </table> <p>Es buena práctica en Drupal que todas los nombres de funciones que declaremos se precedan con el nombre de nuestro módulo. Asi nos evitaremos sorpresas desagradables, ya que si dos funciones en distintos módulos se llaman igual, tendremos un error de PHP.</p> <p> </p> <h3>Cargar incidencia</h3> <p>La primera función de acceso a datos que necesitamos va a ser para obtener los datos de una incidencia de la tabla a partir de su identificador (<em>issue_id</em>).</p> <pre> function issue_control_load_issue($issue_id) { $issue = NULL; if (isset($issue_id)) { $issue = db_select('{issues}', 'i') -&gt;fields('i') <strong> -&gt;condition('issue_id', $issue_id, '=') </strong> -&gt;execute() -&gt;fetchAssoc(); } return $issue; }</pre> <p>Estamos utilzando en este caso la función <a href="https://api.drupal.org/api/drupal/includes%21database%21database.inc/function/db_select/7.x">db_select()</a> de la API de Drupal para recuperar un único registro mediante la condición de que coincida el <em>issue_id</em> con el que recibimos por parámetro. Esta función nos devolverá un objeto de tipo <a href="https://api.drupal.org/api/drupal/includes%21database%21select.inc/class/SelectQuery/7.x" title="Query builder for SELECT statements.">SelectQuery</a> que este caso estamos ejecutando directamente en la misma línea con <em>execute()</em> y recuperando el registro con <em>fetchAssoc()</em>.</p> <p>La función nos devolverá un array con los valores de los campos hemos especifiquedo en <em>fields</em>, en este caso todos. Para acceder mas tarde a los datos de los campos lo haremos así:</p> <pre> $issue = issue_control_load_issue($issue_id); print ($issue['title']); </pre> <h3> </h3> <h3>Crear incidencia</h3> <p>La siguiente función que vamos a necesitar será para dar de alta una nueva incidencia en la tabla. El código es el siguiente:</p> <pre> /** * Creates an issue. */ function issue_control_add($form_state) { global $user; <strong> $issue_id = db_insert('{issues}') -&gt;fields(array( 'title' =&gt; $form_state['values']['title'], 'description' =&gt; $form_state['values']['description'], 'uid' =&gt; $user-&gt;uid, )) -&gt;execute();</strong> $message = t('New issue [@issue_id] has been created by uid [@uid].', array('@issue_id' =&gt; $issue_id, '@uid' =&gt; $user-&gt;uid) ); watchdog('watchdog_form', $message); drupal_set_message($message); } </pre> <p>En esta función estamos recibiendo como parámetro un array <em>$form_state</em>, que es que nos llega cuando se envía el formulario que construiremos mas tarde. De momento podeis ver que este <em>$form_state </em>obtenemos los valores de los campos para guardar el registro.</p> <p>El otro dato que necesitamos a la hora de crear la incidencia es el identificador del usuario que la esta creando. Para ello usamos la variable global <em>$user</em> de Drupal, de la que podemos obtenemos el <em>uid</em>.</p> <p>Aquí la parte importante es la función <a href="https://api.drupal.org/api/drupal/includes%21database%21database.inc/function/db_insert/7.x">db_insert()</a> de Drupal que es la encargada de guardar dicho registro. La sintaxis es muy sencilla, recibe por parámetro el nombre de la tabla y después usamos el método <em>fields</em> para añadir los campos y sus valores, y llamamos al <em>execute()</em> para lanzar la consulta.</p> <p>A continuación estamos creando un mensaje con información del registro que acabamos de crear con el que vamos a hacer dos cosas:</p> <ul> <li>Lo guardamos al <em>watchdog</em> de Drupal, para dejar un registro de cuando se creó.</li> <li>Lo pasamos a <em>drupal_set_message()</em>, y Drupal lo mostrará en la siguiente página, para informar al usuario.</li> </ul> <p>Esta función no devuelve ningún valor, aunque deberíamos añadir al menos un control de errores.</p> <h3> </h3> <h3>Modificar incidencia</h3> <p>Vamo ahora con la modificación de datos de una incidencia. En este caso también vamos a recibir por parámetro un array <em>$form_state</em> que vendrá del envío del formulario de edición.</p> <pre> function issue_control_edit($form_state) { global $user; db_update('{issues}') -&gt;fields(array( 'issue_id' =&gt; $form_state['values']['issue_id'], 'title' =&gt; $form_state['values']['title'], 'description' =&gt; $form_state['values']['description'], 'uid' =&gt; $user-&gt;uid, )) -&gt;condition('issue_id', $form_state['values']['issue_id'], '=') -&gt;execute(); $message = t('Issue [@issue_id] has been edited by uid [@uid].', array('@issue_id' =&gt; $form_state['values']['issue_id'], '@uid' =&gt; $user-&gt;uid) ); watchdog('watchdog_form', $message); drupal_set_message($message); }</pre> <p>Igual que en el caso de la creación del registro, usamos la variable global <em>$user</em> para obtener el uid del usuario que esta modificando la incidencia.</p> <p>Para este caso, utlizamos la función <a href="https://api.drupal.org/api/drupal/includes%21database%21database.inc/function/db_update/7.x">db_update()</a> de Drupal, y de forma similar a la creación del registro, obtenemos los valores de los campos del array <em>$form_state</em>, y establecemos que la condición para modificar los registros es la coincidencia por el issue_id con el que hemos recibido.</p> <p>De la misma forma que en el caso anterior, componemos un mensaje que se manda al watchdog y se muestra al usuario.</p> <p> </p> <h3>Eliminar incidencia</h3> <p>La última función de acceso a datos es para borrar incidencias de la tabla.</p> <pre> function issue_control_delete($issue_id) { global $user; db_delete('{issues}') -&gt;condition('issue_id', $issue_id) -&gt;execute(); $message = t('Issue [@issue_id] has been deleted by uid [@uid].', array('@issue_id' =&gt; $issue_id, '@uid' =&gt; $user-&gt;uid) ); watchdog('watchdog_form', $message); drupal_set_message($message); }</pre> <p>En este caso también es necesario recibir como parámetro el identificador de la incidencia para poder eliminarla utilizando <a href="https://api.drupal.org/api/drupal/includes%21database%21database.inc/function/db_delete/7.x">db_delete()</a> de Drupal.</p> <p>Como en los casos anteriores, componemos un mensaje informativo y lo enviamos al watchdog y al usuario.</p> <p> </p> <h2>El formulario de creación y modificación</h2> <p>Una vez tenemos nuestra API de acceso a base de datos, solo nos queda contruir el formulario de creación y modificación de incidencias, y la llamada para eliminarlas.</p> <p>Para construir el formulario vamos a utilizar la Form API de Drupal, que nos permite definir un formulario completo utilizando un array asociativo para definir los controles.</p> <p>La función se llamara <em>issue_control_form()</em> y quedará así, atención a las partes en negrita:</p> <pre> function issue_control_form($form, &amp;$form_status, <strong>$issue_id = NULL</strong>) { <strong>$issue = issue_control_load_issue($issue_id); </strong> $form['issue_id'] = array( '#type' =&gt; '<strong>textfield</strong>', '#title' =&gt; 'Id', '#required' =&gt; TRUE, <strong>'#disabled' =&gt; TRUE,</strong> '#default_value' =&gt; isset($issue) ? <strong>$issue['issue_id']</strong> : '', ); $form['title'] = array( '#type' =&gt; '<strong>textfield</strong>', '#title' =&gt; 'Title', '#required' =&gt; TRUE, '#default_value' =&gt; isset($issue) ? <strong>$issue['title']</strong> : '', ); $form['description'] = array( '#type' =&gt; '<strong>textarea</strong>', '#title' =&gt; 'Description', '#default_value' =&gt; isset($issue) ? <strong>$issue['description']</strong> : '', ); if (isset($issue)) { $submit_message = t('Save issue'); } else { $submit_message = t('Create issue'); } $form['submit_button'] = array( '#type' =&gt; '<strong>submit</strong>', '#value' =&gt; <strong>$submit_message</strong>, ); return $form; }</pre> <p>El formulario en este caso es muy sencillo, con tres campos y botón para envíar el formulario:</p> <ul> <li>Identificador <em>issue_id</em>: Será un campo de texto sencillo (<em>textfield</em>), pero lo vamos a desactivar para que el usuario no pueda escribir en el.</li> <li>Campo <em>title</em>: el título de la incidencia. Otro campo tipo <em>textfield</em>.</li> <li>Campo description: Descripción de la incidencia. Este campo es mas largo, asi que usaremos un tipo textarea para mostrar una caja de texto grande con multiples líneas. Este tipo de campo puede configurarse también para usar un editor de texto enriquecido.</li> </ul> <p>Usando Bootstrap se vería algo parecido a esto:</p> <div alt="Formulario de incidencias" data-embed-button="file_browser" data-entity-embed-display="image:image" data-entity-embed-display-settings="full_width_shadow" data-entity-type="file" data-entity-uuid="45c3a44d-280c-4355-a0df-fe52fac08248" title="Formulario de incidencias" data-langcode="es" class="embedded-entity"> <img src="/sites/default/files/styles/full_width_shadow/public/formulario_de_incidencias_0.png?itok=OG9QfK2S" alt="Formulario de incidencias" title="Formulario de incidencias" typeof="foaf:Image" class="img-responsive" /> </div> <p>Lo imporante en este caso es que esta función sera utilizada por <em>drupal_get_form()</em>, y puede recibir un tercer parámetro que será el <em>issue_id</em>, en el caso de que sea llamada desde el hook_menu() para modificar. </p> <p>En el caso de recibir ese parámetro, cargamos los datos de esa incidencia mediante <em>issue_control_load_issue()</em>, y ponemos los valores por defecto de los campos a los valores correspondientes, para que el usuario ya vea el formulario relleno con los datos y pueda modificarlos.</p> <p>Dependiendo del caso, ajustaremos tambien el texto del botón de enviar el formulario, mostrando el mensaje correspondiente a cada caso: Guardar o modificar.</p> <p>Ahora necesitamos la función que va a controlar que hacer cuando el usuario envía el formulario:</p> <pre> function issue_control_form_submit($form, &amp;$form_state) { if (empty($form_state['values']['issue_id'])) { issue_control_add($form_state); } else { issue_control_edit($form_state); } drupal_goto('/issues/list'); }</pre> <p>Esta función simplemente comprueba el <em>issue_id</em> que viene al envíar el formulario (en el <em>$form_state</em>) está vacío, en cuyo caso se tratará de una nueva incidencia, y llamamos a nuestra función <em>issue_control_add</em> para crear el nuevo registro.</p> <p>Si por el contrario tenemos un valor en <em>issue_id</em>, se tratará de una moficiación, por lo que se llama a <em>issue_control_edit</em>().</p> <p>En ambos casos, el usuario será redirigido a la página del listado de incidencias.</p> <h2>Eliminando incidencias</h2> <p>Para este ejemplo lo haremos muy sencillo, implementando la función que se llamará desde el <em>hook_menu</em>:</p> <pre> function issue_control_perform_delete($issue_id) { issue_control_delete($issue_id); drupal_goto('/issues/list'); } </pre> <p>Esta función simplemente llama a nuestra función de acceso a datos issue_control_delete() y le pasa el identificador de la incidencia. Una vez eliminada, devolvemos al usuario a la lista de incidencias.</p> <p>Lo suyo sería implementar algún mecanismo para obtener una confirmación del usuario antes de eliminar, para evitar errores, pero no me quiero extender mas con este ejercicio, mejor lo dejamos para otro rato. Si alguien tiene interés que me deje un comentario.</p> <p> </p> <p>Espero que el ejemplo no haya quedado demasiado espeso y que os ayude. A modo de cierre un par de cosas que quedan en el aire:</p> <ul> <li>Habria que sacar el HTML de la lista de incidencias que vimos en la primera parte a un template.</li> <li>Ojo porque no hemos hablado nada de seguridad. Todas las funciones de base de datos de Drupal que estamos usando estan protegidas contra inyección de SQL, pero es una buena práctica utilizar la funcion <a href="https://api.drupal.org/api/drupal/includes%21bootstrap.inc/function/check_plain/7.x">check_plain()</a> cada vez que vayamos a pintar información que viene de la base de datos.</li> </ul> <p>Muchas gracias y saludos</p> <p> </p> </div> </div> <div class="group-footer"> <section> <h2>Comments</h2> <article data-comment-user-id="0" id="comment-193" about="/en/comment/193" typeof="schema:Comment" class="js-comment"> <mark class="hidden" data-comment-timestamp="1619643785"></mark> <footer> <article typeof="schema:Person" about="/user/0"> </article> <p><span rel="schema:author">Subido por <span lang="" typeof="schema:Person" property="schema:name" datatype="">Milton Vintimilla (no verificado)</span> el Mié, 13/11/2019 - 03:01</span> <span property="schema:dateCreated" content="2019-11-13T02:01:55+00:00" class="hidden"></span> </p> <a href="/en/comment/193#comment-193" hreflang="en">Enlace permanente</a> </footer> <div> <h3 property="schema:name" datatype=""><a href="/en/comment/193#comment-193" class="permalink" rel="bookmark" hreflang="en">Consulta abrir venta de un link de una consulta base de datos</a></h3> <div property="schema:text" class="field field--name-comment-body field--type-text-long field--label-hidden field--item"><p>Muy interesante y completo sus explicación sobre las base de datos, me queda una inquietud, si me podría ayudar al listar los datos de una base datos y crear un link que abra una venta nueva y pida datos para una nueva consulta.<br /> Gracias.</p> </div> <drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=193&amp;1=default&amp;2=en&amp;3=" token="vFFEYqK7Sd5IpVkr_-UNEvYZgzZqj38uw4l2Eq-yA4Y"></drupal-render-placeholder> </div> </article> <article data-comment-user-id="0" id="comment-778" about="/en/comment/778" typeof="schema:Comment" class="js-comment"> <mark class="hidden" data-comment-timestamp="1619644398"></mark> <footer> <article typeof="schema:Person" about="/user/0"> </article> <p><span rel="schema:author">Subido por <span lang="" typeof="schema:Person" property="schema:name" datatype="">javier Orti (no verificado)</span> el Lun, 04/01/2021 - 21:02</span> <span property="schema:dateCreated" content="2021-01-04T20:02:09+00:00" class="hidden"></span> </p> <a href="/en/comment/778#comment-778" hreflang="en">Enlace permanente</a> </footer> <div> <h3 property="schema:name" datatype=""><a href="/en/comment/778#comment-778" class="permalink" rel="bookmark" hreflang="en">tutorial</a></h3> <div property="schema:text" class="field field--name-comment-body field--type-text-long field--label-hidden field--item"><p>Esplendido!! me ayudado muchísimo, estoy siguiendo un tutorial sobre un libro de drupal 7 y en esta parte de entidades me había quedado clavado. Gracias a este tutorial me he quedado sin ninguna duda.Muchas gracias.</p> </div> <drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=778&amp;1=default&amp;2=en&amp;3=" token="TZpZIjrvaE8dVujgUkiBxOApPYOxt7mgKresV6AYcbQ"></drupal-render-placeholder> </div> </article> <h2>Añadir nuevo comentario</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=37&amp;2=comment&amp;3=comment" token="gvF5wqRkvRYszrMOWAxHDjMIL25UoMLzXWdz7viFYPw"></drupal-render-placeholder> </section> </div> </article> Fri, 17 Nov 2017 21:26:38 +0000 root 37 at https://www.carloscarrascal.com Acceso a base datos en Drupal 7 https://www.carloscarrascal.com/blog/acceso-base-datos-en-drupal-7 <article data-history-node-id="36" class="node node--type-blog-post node--view-mode-rss group-one-column ds-2col-stacked-fluid clearfix"> <div class="group-header"> <div class="field field--name-node-title field--type-ds field--label-hidden field--item"><h1> Acceso a base datos en Drupal 7 </h1> </div> <div class="field field--name-node-post-date field--type-ds field--label-hidden field--item">Sábado, Noviembre 11, 2017 - 14:50</div> </div> <div class="group-left"> <div class="field field--name-field-tags field--type-entity-reference field--label-hidden field--items"> <div class="field--item"><a href="/tags/d7" hreflang="es">D7</a></div> <div class="field--item"><a href="/tags/drupal" hreflang="es">Drupal</a></div> <div class="field--item"><a href="/tags/database" hreflang="es">Database</a></div> <div class="field--item"><a href="/tags/issues" hreflang="es">Issues</a></div> </div> <div class="field field--name-body field--type-text-with-summary field--label-hidden field--item"><p>Estamos haciendo unas sesiones de formación en el trabajo con Drupal 7, y nos ha quedado un ejercicio bastante completo que creo que merece la pena publicar por si puede ayudar a alguien mas.</p> <p>La idea es mostrar cómo podemos crear un módulo en Drupal 7 que cree su propia tabla en la base de datos, y realizar operaciones sobre ella usando la API de acceso a datos de Drupal.</p> <p>Para este ejemplo vamos a crear un módulo que nos permita gestionar una tabla sencilla de incidencias (<em>Issues</em>). Los objetivos va a ser:</p> <ul> <li>Crear una nueva tabla en la base de datos de Drupal.</li> <li>Listar todos los registros de la tabla</li> <li>Crear nuevos registros mediante un formulario</li> <li>Modificar los datos de un registro</li> <li>Eliminar registros de la tabla</li> </ul> <p>Voy a partir el ejercicio en <a href="/blog/acceso-base-datos-en-drupal-7-parte-2">dos artículos</a> para que no me quede un ladrillo infumable, a ver si lo consigo.</p> <p>Antes de empezar un par de cosas:</p> <ul> <li>Estoy usando un Drupal 7 corriente y moliente. Para este ejemplo no necesitamos instalar ninguna dependencia (de módulos).</li> <li>El tema de presentación que uso es <a href="https://www.drupal.org/project/bootstrap">Bootstrap</a>, así que el HTML que vamos a ver usará algunas clases de Bootstrap que nos ayudará un poco con la presentación. No es realmente necesario y el HTML tendréis que adaptarlo según vuestras necesidades.</li> <li>Todos los nombres de campos, tablas, etc. van a ir <em>en inglés</em>. Los textos de la interfaz (botones, etiquetas, etc.) también, ya que vamos a usar siempre la función <em>t() </em>de Drupal para traducir.</li> </ul> <p>Comencemos.</p> <p> </p> <h2>Creando nuestro módulo</h2> <p>Para el ejemplo vamos a crear un nuevo módulo para el control de nuestra tabla de incidencias, que llamaremos <em>issue_control</em>. Lo creamos en el directorio estándar:</p> <pre> sites/all/modules/custom/issue_control</pre> <p>Tan sólo vamos a necesitar tres archivos:</p> <ul> <li><em>issue_control.info</em>: Archivo de control del módulo.</li> <li><em>issue_control.module</em>: Aquí tendremos nuestro código PHP.</li> <li><em>issue_control.install</em>: Lo usaremos para definir la tabla en la base de datos.</li> </ul> <p>Para empezar, será suficiente con completar el archivo <em>info</em>, con algo parecido a esto:</p> <pre> name = Issues control project = "issue_control" description = "Store information into a database table" package = Custom core = 7.x version = "7.x-0.0-rc1"</pre> <p>De momento, con esto nos vale para comenzar. Ojo, es mejor <strong>no instalar o activar el módulo todavía</strong>, hasta que tengamos la definición del esquema de base de datos. Vamos a ello, a ver como creamos nuestra tabla.<br />  </p> <h2>Hook Schema: Creando nuestra tabla</h2> <p>Perfecto, ya tenemos nuestro módulo. Ahora la idea es que al instalar el módulo, este nos cree automáticamente la tabla que necesitamos en la base de datos. Para ello vamos a usar el <a href="https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_schema/7.x">hook_schema()</a>, que vamos a implementar en el archivo <em>issue_control.install</em> que acabamos de crear.</p> <p>Pero primero vamos a ver que campos necesitamos en nuestra tabla. Hagamos algo sencillo:</p> <table class="table table-striped"> <thead> <tr> <th scope="col">Campo</th> <th scope="col">Tipo</th> <th scope="col">Nulo</th> <th scope="col">Descripción</th> </tr> </thead> <tbody> <tr> <td>issue_id</td> <td>Int</td> <td>NO</td> <td>El identificador de la incidencia. Debería ser una secuencia o un <em>autoincrement</em>. No puede ser nulo, ya que lo vamos a usar como identificador y <em>primary key</em>. En Drupal usaremos el tipo <em>serial</em>, que nos permite hacer justamente eso y no tener que preocuparnos de rellenarlo nosotros.</td> </tr> <tr> <td>title</td> <td>String (255)</td> <td>NO</td> <td>Título de la incidencia. Una cadena de caracteres no muy larga. No queremos que se quede vacío. En Drupal usaremos el tipo <em>varchar</em>.</td> </tr> <tr> <td>description</td> <td>String (Largo)</td> <td> </td> <td>Este será el campo para guardar el contenido de la incidencia. Queremos que sea una <em>text area</em> donde el usuario pueda meter mucho texto. Para esto en Drupal tenemos el tipo <em>text</em>.</td> </tr> <tr> <td>uid</td> <td>Int</td> <td>NO</td> <td>Vamos a incluir el id del usuario (<em>uid</em>) que crea o modifica la incidencia, para que nos dé un poco de juego el ejercicio. Este sera un campo numérico sencillo de tipo <em>int </em>en nuestro Drupal.</td> </tr> </tbody> </table> <p>Bien, ahora que tenemos mas o menos claro los campos que queremos, vamos a crear nuestro <em>hook_schema</em>. Este hook debe ser implementado en el archivo <em>.install</em> del módulo, y nos va a permitir declarar una o varias tablas que se crearán cuando se instale el módulo (ojo: instalación, no activación) y se eliminarán automáticamente cuando se desinstale (ojo otra vez: desinstalar, no deshabilitar).</p> <p>Vamos a ver el código:</p> <pre> &lt;?php /** * Implements hook_schema(). */ function issue_control_schema() { $schema['issues'] = array( 'description' =&gt; 'Table for storing issues', 'fields' =&gt; array( 'issue_id' =&gt; array( 'type' =&gt; 'serial', 'unsigned' =&gt; TRUE, 'not null' =&gt; TRUE, 'description' =&gt; 'Issue ID', ), 'title' =&gt; array( 'type' =&gt; 'varchar', 'length' =&gt; 255, 'not null' =&gt; TRUE, 'default' =&gt; '', 'description' =&gt; 'Issue title', ), 'description' =&gt; array( 'type' =&gt; 'text', 'size' =&gt; 'normal', 'description' =&gt; 'Issue description', ), 'uid' =&gt; array( 'type' =&gt; 'int', 'unsigned' =&gt; TRUE, 'not null' =&gt; TRUE, 'default' =&gt; 0, 'description' =&gt; 'User uid for the issue', ), ), 'primary key' =&gt; array('issue_id'), 'indexes' =&gt; array( 'issueid' =&gt; array('issue_id'), ), ); return $schema; } </pre> <p>La referencia del <a href="https://api.drupal.org/api/drupal/includes%21database%21schema.inc/group/schemaapi/7.x">Schema API</a> de Drupal tiene bastante miga, y daría para varios artículos, pero no es el objetivo de este post. Recomiendo darle un buen repaso a la documentación, aunque no he podido encontrarla en castellano.</p> <p>Es importante tener en cuenta que si tenéis errores en este fichero o en este hook, lo más probable es que pete al instalar el módulo y no cree nada. Para que Drupal vuelva a ejecutar el <em>hook_schema</em> después de una prueba fallida, teneis que <strong>desinstalar el módulo por completo y volver a instalar</strong>. Podeis usar <em>drush pmu issue_control</em>, o bien utilizar el módulo <a href="https://www.drupal.org/project/devel">Devel</a>, que tiene una opción para reinstalar módulos muy útil para esto, hasta que consigáis que funcione correctamente la creación de la tabla.</p> <p> </p> <h2>Listado de registros de la tabla</h2> <p>Lo primero que vamos a hacer es preparar una página para mostrar el listado de las incidencias guardadas en la tabla. Logicamente, al principio estará vacía si acabamos de instalar el módulo. Podemos crear algunos registros a mano, o bien utilizando una herramienta como <a href="https://www.phpmyadmin.net/">phpMyAdmin</a>. Una ejemplo sencillo para tener algunos datos de prueba podría ser usando <em>Drush</em>:</p> <pre> drush sql-cli insert into issues (title, description, uid) values ('Un titulo', 'Description de pruebas', 1); </pre> <p>Para hacer nuestro listado vamos a utilizar dos funciones en nuestro archivo <em>.module</em>:</p> <ul> <li>Un <em>hook_menu</em> para definir la URL que usaremos para la lista, y la función a llamar para pintarla.</li> <li>Una función nuestra que devolverá el listado de incidencias en formato HTML.</li> </ul> <p>Primero, vamos a crear un <em>hook_menu</em>, y definiremos la URL <strong><em>/issues/list</em></strong> para nuestro listado de incidencias. De esta forma, cuando carguemos la URL <em><a href="http://misitio.drupal/issues/list">http://misitio.drupal/issues/list</a></em>, obtendremos la lista de incidencias.</p> <p>En nuestro archivo <em>.module</em>, declaramos el <em>hook_menu</em>:</p> <pre> /** * Implements hook_menu(). * */ function issue_control_menu() { $items = array(); $items['issues/list'] = array( 'title' =&gt; 'Issues List', 'description' =&gt; 'Issues list from the database.', 'page callback' =&gt; 'issue_control_list', 'page arguments' =&gt; array(), 'access callback' =&gt; TRUE, ); return $items; }</pre> <p>Con este <em>hook</em> definimos la URL y especificamos que al cargar dicha URL, se ejecutará la función <em>issue_control_list()</em> para pintar los resultados.</p> <p>Después implementamos esa función para listar:</p> <pre> /** * Shows a list with all issues in the database table. */ function issue_control_list() { $content = '&lt;table class="table table-hover table-striped"&gt;'; $content .= '&lt;tr&gt;'; $content .= '&lt;th&gt;' . t('Issue Id') . '&lt;/th&gt;'; $content .= '&lt;th&gt;' . t('Title') . '&lt;/th&gt;'; $content .= '&lt;th&gt;' . t('Description') . '&lt;/th&gt;'; $content .= '&lt;th&gt;' . t('Username') . '&lt;/th&gt;'; $content .= '&lt;th&gt;' . t('Actions') . '&lt;/th&gt;'; $content .= '&lt;/tr&gt;'; $results = db_query('SELECT * FROM {issues}'); foreach ($results as $row) { $user = user_load($row-&gt;uid); $content .= '&lt;tr&gt;'; $content .= '&lt;td&gt;' . $row-&gt;issue_id . '&lt;/td&gt;'; $content .= '&lt;td&gt;' . $row-&gt;title . '&lt;/td&gt;'; $content .= '&lt;td&gt;' . $row-&gt;description . '&lt;/td&gt;'; $content .= '&lt;td&gt;' . (!empty($user-&gt;name) ? $user-&gt;name : t('Anonymous')) . '&lt;/td&gt;'; $content .= '&lt;td class="btn-toolbar"&gt;'; $content .= '&lt;a class="btn btn-xs btn-primary" href="' . url('/issues/edit/' . $row-&gt;issue_id) . '"&gt;' . t('Edit') . '&lt;/a&gt;'; $content .= '&lt;a class="btn btn-xs btn-danger" href="' . url('/issues/delete/' . $row-&gt;issue_id) . '"&gt;' . t('Delete') . '&lt;/a&gt;'; $content .= '&lt;/td&gt;'; $content .= '&lt;/tr&gt;'; } $content .= '&lt;/table&gt;'; $content .= '&lt;a class="btn btn-success" href="' . url('/issues/create') . '"&gt;' . t('Add an issue') . '&lt;/a&gt;'; return $content; }</pre> <p>Con este código estamos obteniendo todos los registros de la tabla issues y pintando una tabla HTML para mostrar los resultados. Esta función tiene varias partes interesantes.</p> <ol> <li>Se utiliza una variable <em>$content</em> para ir guardando el HTML que vamos construyendo.</li> <li>Primero creamos las cabeceras de la tabla con los nombres de los campos.</li> <li>Después, se lanza la consulta a la base de datos para pedir todos los registros de la tabla <em>issues.</em></li> <li>A continuación se utiliza un bucle para pintar cada uno de los registros. Para cada registro cargaremos el usuario de Drupal correspondiente al <em>uid</em> que venga en la issue, y pintamos su nombre.</li> <li>Las clases CSS que estoy utilizando son para <em>Bootstrap</em>, por ejemplo, los <em>table</em> y <em>table-striped</em> nos proporcionarán una tabla más pintona, o los <em>btn</em> y <em>btn-primary</em> para los botones de acción.</li> </ol> <p> </p> <h2>Obteniendo los datos</h2> <p>Vamos a ver un poco mas en detalle cómo estamos obteniendo los registros de la base de datos. En este caso como es una <em>query</em> muy sencilla, usaremos la función <a href="https://api.drupal.org/api/drupal/includes%21database%21database.inc/function/db_query/7.x">db_query</a> de Drupal:</p> <pre> $results = db_query('SELECT * FROM {issues}');</pre> <p>Esta llamada nos devuelve el resultado de la <em>query</em> como un array en la variable <em>$results</em>. Un detalle importante es utilizar el nombre de la tabla entre llaves, ya que Drupal lo reemplazará por la tabla correcta si nuestro sitio usa un prefijo (<em>prefix</em>) para la base de datos.</p> <p>Este array tendrá un objeto de tipo <em>stdClass</em> por cada registro de la base de datos que devuelva la consulta, y en cada uno de estos objetos podremos acceder a los campos de la tabla como si fuesen propiedades, por ejemplo <em>$row-&gt;title</em>, nos devolverá el título. </p> <p>De esta forma, para leer y pintar los registros usaremos un bucle, e iremos recogiendo los valores de cada campo que queremos mostrar:</p> <pre> foreach ($results as <strong>$row</strong>) { $user = user_load(<strong>$row-&gt;uid</strong>); // Aquí cargamos el usuario correspondiente por su uid.   $content .= '&lt;tr&gt;'; $content .= '&lt;td&gt;' . <strong>$row-&gt;issue_id</strong> . '&lt;/td&gt;'; // Pintamos el campo issue_id. ... } </pre> <p>No debemos usar <em>db_query</em> para realizar operaciones de <em>insert</em>, <em>update</em> o <em>delete</em>, y si necesitamos realizar operaciones con la query antes de ejecutarla, es mejor usar la función <em>db_select</em> que veremos más adelante.</p> <p> </p> <h2>Resultados</h2> <p>Si todo ha ido bien tendremos algo parecido a este ejemplo que se ve bastante resultón gracias a <em>Bootstrap</em>:</p> <div alt="Listado de incidencias" data-embed-button="file_browser" data-entity-embed-display="image:image" data-entity-embed-display-settings="full_width_shadow" data-entity-type="file" data-entity-uuid="12b43b0e-79fd-423c-9406-77c3bc0a05a8" title="Listado de incidencias" data-langcode="es" class="embedded-entity"> <img src="/sites/default/files/styles/full_width_shadow/public/listado_de_incidencias_com_tema_bootstrap.png?itok=KcEHMVOo" alt="Listado de incidencias" title="Listado de incidencias" typeof="foaf:Image" class="img-responsive" /> </div> <p>Como podéis ver, estamos ya pintando los botones de acción para cada registro: <em>Edit</em> y <em>Delete</em>, que son enlaces a otras páginas que aún no existen, y después de la tabla tenemos otro botón para crear una incidencia nueva. Estos enlaces serán:</p> <ul> <li>Un enlace <em>/issues/edit/[issue_id]</em> para modificar los datos de una incidencia.</li> <li>Otro a <em>/issues/delete/[issue_id]</em> para eliminar incidencias de la tabla.</li> <li>Por último, <em>/issues/create</em> para dar de alta incidencias desde un formulario de Drupal.</li> </ul> <p>Estas tres acciones las veremos en el próximo artículo, en el que os dejaré un enlace para descargar todo el código del ejemplo completo.</p> <p>Como pequeña puntualización, esta función para pintar la lista tiene demasiado código HTML. Lo correcto sería <em>utilizar una plantilla o template</em> para pintar la lista, de forma que todo el HTML estaría dentro del <em>template</em>. Hay mil ejemplos en internet para utilizar <em>templates</em> en nuestros módulos con Drupal 7, pero aún así pienso escribir otro artículo después de estos para mostrar cómo hacerlo en condiciones con este ejemplo.</p> <p>Muchas gracias y hasta pronto.</p> <p> </p> <p><a href="/blog/acceso-base-datos-en-drupal-7-parte-2">La segunda parte de este artículo esta aquí.</a></p> </div> </div> <div class="group-footer"> <section> <h2>Añadir nuevo comentario</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=36&amp;2=comment&amp;3=comment" token="5eDklJfT4NxvvLxFLpKSLCtmXzwwAdpl5L5jpEDPysM"></drupal-render-placeholder> </section> </div> </article> Sat, 11 Nov 2017 13:50:48 +0000 root 36 at https://www.carloscarrascal.com Rendimiento en Drupal: Google Page Speed https://www.carloscarrascal.com/blog/rendimiento-en-drupal-google-page-speed <article data-history-node-id="34" class="node node--type-blog-post node--view-mode-rss group-one-column ds-2col-stacked-fluid clearfix"> <div class="group-header"> <div class="field field--name-node-title field--type-ds field--label-hidden field--item"><h1> Rendimiento en Drupal: Google Page Speed </h1> </div> <div class="field field--name-node-post-date field--type-ds field--label-hidden field--item">Viernes, Octubre 20, 2017 - 21:44</div> </div> <div class="group-left"> <div class="field field--name-field-tags field--type-entity-reference field--label-hidden field--items"> <div class="field--item"><a href="/tags/drupal" hreflang="es">Drupal</a></div> <div class="field--item"><a href="/tags/rendimiento" hreflang="es">Rendimiento</a></div> </div> <div class="field field--name-body field--type-text-with-summary field--label-hidden field--item"><p><a href="https://developers.google.com/speed/pagespeed/">Page Speed</a> es una herramienta de Google que nos puede ayudar a optimizar el tiempo de carga de nuestro sitio Drupal. No es que lo vaya a arreglar por nosotros, pero al menos nos puede dar varias ideas interesantes de por dónde van los tiros. Para utilizarlo nos va a pedir una URL del sitio que queramos probar, y nos puntúa de 0 a 100 el rendimiento de esa URL teniendo en cuenta varios factores.</p> <p>Sin tocar demasiado de la configuración inicial de Drupal 8, mi sitio estaba alrededor de un 60/65 calificado como Pobre (Poor), tanto para escritorio como para móviles. Para Drupal 7 y sin grandes esfuerzos en sitios pequeños o medianos, es fácil subir por encima de 90 en ambas puntuaciones.</p> <p>Cabe destacar que para poder puntuar bien en el apartado de dispositivos móviles, lo primero es que nuestro <a href="https://es.wikipedia.org/wiki/Dise%C3%B1o_web_adaptable">tema sea adaptable</a> (Responsive). En mi caso estoy utilizando Bootstrap como tema base, que puede que no sea lo mas puntero a día de hoy pero funciona muy bien. A estas alturas puede que parezca una tontería siquiera tener que recordarlo, pero es que aun se ve cada cosa por ahí que asusta.</p> <p>En mi caso, para este sitio, las áreas de mejora eran bastante comunes, vamos a verlas:</p> <h2>Optimizar imágenes</h2> <p>Este es un problema bastante común: las imágenes que mostramos en nuestro sitio son demasiado grandes o no están optimizadas.</p> <p>El primer paso es revisar todas las imágenes de estructura que estemos usando en nuestro diseño, es decir, logos, imágenes para bullets, fondos, etc. que utilice nuestro tema. Por ejemplo, este sitio tiene cuatro imágenes en el pie que van a 40px de alto. Si van fijas a 40px con CSS, pues las imágenes no deberían ser más grandes de ese tamaño, y <em>Page Speed</em> lo va a comprobar.</p> <p>Lo siguiente sería asegurarse que todas las imágenes en contenidos están utilizando un <em>Estilo de imagen</em> apropiado (<em>Image styles</em>) cuando se pintan en la página, con la resolución apropiada. En este sitio, para la versión de escritorio las imágenes que se muestran en los artículos no van ser nunca más anchas de 850px, porque así está definido en el diseño. Prestad atención a las imágenes que van en los teasers, destacados en bloques, etc.</p> <p>Por último, pensemos en los dispositivos móviles. Siguiendo con el ejemplo anterior, si nuestras imágenes son de 850px de ancho, van a ser demasiado grandes para cualquier teléfono. La guinda del pastel es tener configurados diferentes estilos de imágenes para cada uno de los saltos de resolución o media queries que tengamos en nuestro tema. Para esto es muy útil el módulo <a href="https://www.drupal.org/project/breakpoints">Breakpoints</a>, que en Drupal 8 se incluyó en el core.</p> <p>Otro módulo útil para Drupal 8 es <a href="https://www.drupal.org/project/entity_embed">Entity embed</a>, que aunque aún está en beta, funciona bastante bien y nos permite junto con <a href="https://www.drupal.org/project/entity_browser">Entity browser</a> añadir imágenes desde el CKEditor y seleccionar el Image style en que se van a mostrar, con lo que podremos tener todas las imágenes del contenido controladas y responsive.</p> <p>El objetivo final de este proceso de cara a mejorar el rendimiento de nuestro sitio es sencillo: si nuestras imágenes están bien optimizadas, tendrán un menor tamaño, se enviarán menos datos por la red, y tendremos un mejor tiempo de carga.</p> <p> </p> <h2>Minificar archivos CSS y Javascript</h2> <p>Compactar nuestros archivos de estilos y Javascript puede ahorrar un buen montón de bytes que nos ayudará a mejorar otro poquito el rendimiento.</p> <p>El primer paso en este sentido es asegurarse que tenemos activado en el apartado de Configuración / Rendimiento las opciones para agrupar CSS y Javascript, como muestra la siguiente captura.</p> <div alt="Configuración de rendimiento en Drupal 8" data-embed-button="file_browser" data-entity-embed-display="image:image" data-entity-embed-display-settings="full_width" data-entity-type="file" data-entity-uuid="f0ce0fd4-5147-45c9-8684-8ac6ee97a285" title="Configuración de rendimiento en Drupal 8" data-langcode="es" class="embedded-entity"> <img src="/sites/default/files/styles/full_width/public/drupal-rendimiento-configuracion-css-y-javascript.png?itok=Q9GHADw1" alt="Configuración de rendimiento en Drupal 8" title="Configuración de rendimiento en Drupal 8" typeof="foaf:Image" class="img-responsive" /> </div> <p>A partir de aquí el tema se complica un poco mas. Aunque tengamos el mejor y más bonito tema del mundo para nuestro sitio, perfectamente estructurado con Gulp, tirando de mil paquetes de NodeJS, y podamos compilar nuestro SASS o Less y minificarlo nosotros mismos, vamos a estar usando montones de librerías de terceros, código JS y CSS de otros módulos Drupal, etc. que no tiene porqué estar tan bonito como el nuestro.</p> <p>Para remediar esto yo he optado por instalar el módulo <a href="https://www.drupal.org/project/advagg">Advanced CSS/JS Aggregation</a>, que ya habia estado usando en Drupal 7, y nos permite configurar un montón de opciones, entre ellas, minificar todo el código JS y CSS, sin muchas complicaciones, como se puede ver en las siguientes capturas.</p> <div alt="Minificar Javascript con AdvAgg" data-embed-button="file_browser" data-entity-embed-display="image:image" data-entity-embed-display-settings="full_width_shadow file" data-entity-type="file" data-entity-uuid="f74e2f12-297d-440b-8a8e-fbf3264da7c9" title="Minificar Javascript con AdvAgg" data-langcode="es" class="embedded-entity"> <a href="https://www.carloscarrascal.com/sites/default/files/adv-agg-javascript-minificacion.png"><img src="/sites/default/files/styles/full_width_shadow/public/adv-agg-javascript-minificacion.png?itok=U0mi1FYH" alt="Minificar Javascript con AdvAgg" title="Minificar Javascript con AdvAgg" typeof="foaf:Image" class="img-responsive" /> </a> </div> <p> </p> <div alt="Minifica" data-embed-button="file_browser" data-entity-embed-display="image:image" data-entity-embed-display-settings="full_width_shadow file" data-entity-type="file" data-entity-uuid="c0293722-40ac-4c81-8b30-aafc72e37954" title="Minificar CSS con AdvAgg" data-langcode="es" class="embedded-entity"> <a href="https://www.carloscarrascal.com/sites/default/files/adv-agg-css-minificacion.png"><img src="/sites/default/files/styles/full_width_shadow/public/adv-agg-css-minificacion.png?itok=m_6B9zVI" alt="Minifica" title="Minificar CSS con AdvAgg" typeof="foaf:Image" class="img-responsive" /> </a> </div> <h2>Especificar caché de navegador</h2> <p>Este caso es especialmente curioso, porque se queja de este fichero:</p> <ul> <li><a href="https://www.google-analytics.com/analytics.js&amp;nbsp">https://www.google-analytics.com/analytics.js</a> (2 horas)</li> </ul> <p>Es el archivo Javascript de Google Analytics, que carga el módulo. Solucionar este problema fué sencillo, el propio módulo tiene una opción para cachear localmente el Javascript, así que la activamos y adiós a el problema.</p> <div alt="Activar cache de Javascript de Google Analytics" data-embed-button="file_browser" data-entity-embed-display="image:image" data-entity-embed-display-settings="full_width_shadow" data-entity-type="file" data-entity-uuid="c2f2390f-8060-48b3-a8b1-3c25c0fd4a18" title="Activar cache de Javascript de Google Analytics" data-langcode="es" class="embedded-entity"> <img src="/sites/default/files/styles/full_width_shadow/public/drupal-rendimiento-cache-google-analytics.png?itok=shJeF29a" alt="Activar cache de Javascript de Google Analytics" title="Activar cache de Javascript de Google Analytics" typeof="foaf:Image" class="img-responsive" /> </div> <p>Normalmente el resto de opciones de caché estarán correctamente configuradas en una instalación normal de Drupal, gracias al sistema de caché y al fichero <em>.htaccess</em> para Apache que viene por defecto con la distribución.</p> <p> </p> <p> </p> <h2>Eliminar el JavaScript que bloquea la visualización y el CSS del contenido de la mitad superior de la página</h2> <p> </p> <p>Este es el punto que mas quebraderos de cabeza me está dando. Con Drupal 7 si he conseguido solucionarlo usando solamente el módulo AdvAgg y jugando con las opciones. Hay una referencia muy buena <a href="https://groups.drupal.org/node/517292">aquí</a> sobre como configurarlo que a mí me ha dado buenos resultados.</p> <p>Ahora bien, con Drupal 8 aún no he conseguido solucionarlo del todo, y este blog me sigue dando el error. He probado varias configuraciones y no ha habido forma:</p> <ul> <li>Estaba usando el módulo <a href="https://www.drupal.org/project/fontyourface">@font-your-face</a> para cargar las Google Fonts. Como uno de los avisos que da Page Speed era precisamente el de la carga de las fuentes, lo he desactivado y he añadido la fuente directamente en el tema. No ha habido ninguna mejora, pero me he quitado un módulo.</li> <li>He probado diferentes opciones del bundler de AdvAgg pero no he conseguido mejoras tampoco.</li> <li>No he podido activar la opción de AdvAgg para cargar el CSS por medio de Javascript, que creo que es lo que podría ayudar, porque al activarlo deja de funcionar el File Browser para gestionar las imágenes al editar contenidos. Voy a seguir investigando por aquí, porque este punto es la diferencia entre la configuración que me funciona para Drupal 7 y Drupal 8</li> </ul> <p>Ampliaré este artículo si consigo encontrar una solución, de momento cualquier comentario al respecto sería bienvenido.</p> <p> </p> <h2>Conclusiones</h2> <p>Después de los ajustes que hemos visto y el uso del módulo AdvAgg, he pasado de un <strong>60/65</strong> a un <strong>75/90</strong>. Aunque quedan algunas cosas por solucionar, está bastante mejor. En mi caso para la versión móvil, aún tengo que optimizar algunas de las imágenes, que es lo que más me está penalizando, y falta encontrar una solución para el bloqueo causado por el CSS, pero sin mucho esfuerzo ya hemos obtenido bastantes mejoras.</p> <p> </p> <p> </p> <h2>Referencias</h2> <ul> <li>Consejos de configuración de AdvAgg: <a href="https://groups.drupal.org/node/517292">https://groups.drupal.org/node/517292</a></li> <li>Añadir fuentes de Google a nuestro tema: <a href="https://duntuk.com/how-add-google-fonts-drupal-8-theme-proper-way">https://duntuk.com/how-add-google-fonts-drupal-8-theme-proper-way</a></li> </ul> </div> </div> <div class="group-footer"> <section> <h2>Añadir nuevo comentario</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=34&amp;2=comment&amp;3=comment" token="gyJKxjJnhi5hUMkjsfm99q-H7byLjfyy-BRz08MQALQ"></drupal-render-placeholder> </section> </div> </article> Fri, 20 Oct 2017 19:44:28 +0000 root 34 at https://www.carloscarrascal.com Bootstrap + Less + Gulp para Drupal 8 https://www.carloscarrascal.com/blog/bootstrap-less-gulp-para-drupal-8 <article data-history-node-id="33" class="node node--type-blog-post node--view-mode-rss group-one-column ds-2col-stacked-fluid clearfix"> <div class="group-header"> <div class="field field--name-node-title field--type-ds field--label-hidden field--item"><h1> Bootstrap + Less + Gulp para Drupal 8 </h1> </div> <div class="field field--name-node-post-date field--type-ds field--label-hidden field--item">Lunes, Octubre 16, 2017 - 21:19</div> </div> <div class="group-left"> <div class="field field--name-field-tags field--type-entity-reference field--label-hidden field--items"> <div class="field--item"><a href="/tags/d8" hreflang="es">D8</a></div> <div class="field--item"><a href="/tags/drupal" hreflang="es">Drupal</a></div> <div class="field--item"><a href="/tags/bootstrap" hreflang="es">Bootstrap</a></div> <div class="field--item"><a href="/tags/gulp" hreflang="es">Gulp</a></div> <div class="field--item"><a href="/tags/less" hreflang="es">less</a></div> <div class="field--item"><a href="/tags/javascript" hreflang="es">Javascript</a></div> </div> <div class="field field--name-body field--type-text-with-summary field--label-hidden field--item"><p><a href="https://gulpjs.com/">Gulp.js</a> es un gestor de tareas en Javascript que nos va ayudar a automatizar tareas repetitivas en nuestro proyecto Drupal. Una de las que mas vamos a repetir mientras estamos desarrollando es compilar el tema para generar el <em>CSS</em> si estamos utilizando un preprocesador como <em>SASS</em> o <em>Less</em>, con el tema de Bootstrap, por ejemplo, o con nuestro propio tema '<em>custom</em>'.</p> <p>Añadir <em>Gulp</em> a nuestro tema para compilar nos proporciona unas cuantas ventajas, entre las que podemos destacar:</p> <ul> <li>Recompilar el tema automáticamente cada vez que modificamos un archivo <em>less</em>.</li> <li>Y recargar automáticamente el navegador cuando cambia el <em>CSS</em>.</li> <li>Minificar el <em>CSS</em> para mejorar el rendimiento.</li> <li>Ejecutar test y chequear el código al compilar el tema, para controlar errores de sintaxis, estándares, etc.</li> <li>Gestionar dependencias y minificar nuestros ficheros <em>Javascript</em>.</li> <li>Realizar operaciones con ficheros: copiar, mover, borrar, etc.</li> </ul> <p>Vamos a ver una forma sencilla y rápida que nos dará un buen punto de partida para comenzar a utilizar <em>Gulp</em> para compilar nuestro tema de Drupal 8 basado en <em>Bootstrap</em>. Yo estoy utilizando la versión normal que viene con Less por defecto. Si estáis usando <em>SASS</em> en vez de Less podéis darle un vistazo a <a href="http://www.abhishekanand.in/article/set-drupal-8-theme-gulp-bower-bootstrap-sass-fontawesome">este artículo</a> (en inglés).</p> <p>Si habéis seguido las instrucciones para crear un subtema de <em>Bootstrap</em>, ya tendréis una estructura parecida a esta:</p> <ul> <li>DRUPAL_ROOT/themes/custom/vuestro_subtema <ul> <li>less <ul> <li>style.less &lt;-- Este el archivo principal Less para compilar el tema.</li> </ul> </li> <li>css <ul> <li>style.css &lt;-- Archivo CSS resultado de compilar el tema.</li> </ul> </li> </ul> </li> </ul> <p>En este punto, y si no queremos complicarnos la vida, podremos compilar nuestro tema de forma sencilla con un comando llamando al compilador de Less:</p> <pre> lessc less/style.less &gt; css/style.css </pre> <p>Yo tenía esta línea metida en un script bash sencillo llamado <em>compile.sh</em>, y lo ejecutaba cuando necesitaba compilar, así:</p> <pre> sh ./compile.sh</pre> <p>Pero claro, si estáis leyendo es que os va la marcha, así que vamos a ver cómo montamos Gulp en un periquete. Primero vamos a necesitar crear en la raíz de nuestro tema (subtema si usais Bootstrap), un archivo package.json para controlar las dependencias de Npm. Será muy sencillo:</p> <pre> { "name": "nombre_del_tema", "version": "0.0.0", "description": "Paquete para compilar subtema de Bootstrap con Gulp", "author": "nombre<your name="">", "contributors": [ { "name": "nombre<your name="">", "email": "direccion@email.com<your email="">" } ], "dependencies": {}, "devDependencies": {}, "license": "Private" } </your></your></your></pre> <p>Obviamente vamos a necesitar tener instalado npm, ya que lo vamos a utilizar para instalar cómodamente todos los paquetes y dependencias que necesitamos.</p> <p>Primero instalamos <em>Gulp</em> como paquete global para no tener problemas con el ejecutable:</p> <pre> npm install -g gulp</pre> <p>Una vez instalado, desde la raíz de nuestro tema, instalamos las dependencias y le decimos a npm que las añada a nuestro fichero <em>package.json</em> con el parámetro <em>--save-dev</em>:</p> <pre> npm install gulp gulp-less gulp-livereload gulp-sourcemaps gulp-watch --save-dev</pre> <p>Después de ejecutar este comando deberíamos tener un directorio <em>node_modules</em> con los paquetes que acabamos de instalar:</p> <pre> ll node_modules/ total 0 drwxr-xr-x 12 charles staff 408 16 Oct 19:36 gulp drwxr-xr-x 8 charles staff 272 16 Oct 19:36 gulp-less drwxr-xr-x 11 charles staff 374 16 Oct 19:36 gulp-livereload drwxr-xr-x 8 charles staff 272 16 Oct 19:36 gulp-sourcemaps drwxr-xr-x 7 charles staff 238 16 Oct 19:36 gulp-watch</pre> <p>Y en nuestro archivo <em>package.json</em> se habrán incluido las dependencias (los números de versión pueden cambiar):</p> <pre> "devDependencies": { "gulp": "^3.9.1", "gulp-less": "^3.3.2", "gulp-livereload": "^3.8.1", "gulp-sourcemaps": "^2.6.1", "gulp-watch": "^4.3.11" }, </pre> <p>Ahora necesitamos crear el archivo de control para <em>Gulp</em>, llamado <em>gulpfile.js</em>, también en la raíz de nuestro tema, con lo básico para empezar a funcionar:</p> <pre> var gulp = require('gulp'); var less = require('gulp-less'); var watch = require('gulp-watch'); var livereload = require('gulp-livereload'); var sourcemaps = require('gulp-sourcemaps'); gulp.task('less', function () { gulp.src('less/style.less') .pipe(sourcemaps.init()) .pipe(less()) .pipe(sourcemaps.write('.')) .pipe(gulp.dest('css')) .pipe(livereload()); }); gulp.task('watch', function() { livereload.listen(); gulp.watch('less/*.less', ['less']); }); gulp.task('default', ['watch']); </pre> <p>Con esto deberíamos poder ejecutar dos comandos para compilar:</p> <pre> gulp less</pre> <p>Que simplemente compilará el tema creando los CSS dentro del directorio css, o también:</p> <pre> gulp watch</pre> <p>Que dejará el proceso corriendo, vigilando modificaciones en nuestros archivos less y recompilando el tema si detecta algún cambio. Este comando se ejecutará por defecto si solo hacemos:</p> <pre> gulp</pre> <p>Vamos a ver en detalle que hemos metido en este fichero de control de <em>Gulp</em>:</p> <ul> <li>Las líneas <em>var</em> van a cargar las dependencias que necesitamos. <ul> <li><strong>Gulp</strong> y <strong>gulp-less</strong>: Carga la libreria y la extension para less.</li> <li><strong>gulp-watch</strong>: Carga el módulo para vigilar los archivos del tema.</li> <li><strong>gulp-livereload</strong>: Carga el módulo <a href="http://livereload.com/">livereload</a> para automatizar la recarga del navegador. Para que funcione tenemos que instalar una <a href="http://livereload.com/extensions/">extensión del navegador</a>. Hay extensiones disponibles para Chrome, Firefox y Safari.</li> <li><strong>gulp-sourcemaps</strong>: Carga el paquete para generar sourcemaps de nuestros ficheros CSS. Tal como está configurado creará un fichero <em>.map</em> separado en el mismo directorio /css. Estos ficheros sirven para ayudar en la depuración de nuestros estilos.</li> </ul> </li> <li>Tarea <strong>less</strong>: <ul> <li>Compila el CSS, aplica el módulo de <em>livereload</em> y genera los sourcemaps necesarios.</li> </ul> </li> <li>Tarea <strong>watch</strong>: <ul> <li>Activa el <em>livereload</em> y vigila los archivos con extension <em>.less</em>, dentro de la carpeta less por si cambian, lo que recompilará el tema completo.</li> </ul> </li> <li>Tarea <strong>default</strong>: <ul> <li>Se ejecuta cuando corremos gulp sin parámetros, y por defecto lanzará la tarea <em>watch</em>.</li> </ul> </li> </ul> <p>Con estos sencillos pasos tendremos configurado <em>Gulp</em> en muy poco tiempo, y a partir de esta configuración inicial podremos ir ampliando e introducir otras mejoras o librerías en nuestro proyecto Drupal.</p> <p> </p> <h2>Referencias</h2> <ol> <li><a href="https://eureka.ykyuen.info/2015/03/09/drupal-7-setup-bootstrap-3-theme-with-gulp-for-less-compilation/">DRUPAL 7 – SETUP BOOTSTRAP 3 THEME WITH GULP FOR LESS COMPILATION</a></li> <li><a href="http://www.abhishekanand.in/article/set-drupal-8-theme-gulp-bower-bootstrap-sass-fontawesome">Set up a Drupal 8 theme with Gulp, Bower, Bootstrap Sass, &amp; FontAwesome</a></li> <li><a href="http://brandonclapp.com/what-is-gulp-js-and-why-use-it/">What is gulp.js and why use it?</a></li> </ol> </div> </div> <div class="group-footer"> <section> <h2>Añadir nuevo comentario</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=33&amp;2=comment&amp;3=comment" token="SOCxtNpb2aUdz8W91NLOhKstqWZQIwlggK7ehFpqvWc"></drupal-render-placeholder> </section> </div> </article> Mon, 16 Oct 2017 19:19:41 +0000 root 33 at https://www.carloscarrascal.com Configuración en Drupal 8. Parte 2: Config Split https://www.carloscarrascal.com/blog/configuracion-en-drupal-8-parte-2-config-split <article data-history-node-id="32" class="has-header-image node node--type-blog-post node--view-mode-rss group-one-column ds-2col-stacked-fluid clearfix"> <div class="group-header"> <div class="field field--name-node-title field--type-ds field--label-hidden field--item"><h1> Configuración en Drupal 8. Parte 2: Config Split </h1> </div> <div class="field field--name-field-header-image field--type-image field--label-hidden field--item"> <img src="/sites/default/files/styles/full_width/public/2017-10/2017-10-08-17-15-08_0.jpg?itok=kgUcKuCT" width="850" height="478" alt="Configuración en Drupal 8. Parte 2: Config Split" typeof="foaf:Image" class="img-responsive" /> </div> <div class="field field--name-node-post-date field--type-ds field--label-hidden field--item">Sábado, Octubre 7, 2017 - 17:26</div> </div> <div class="group-left"> <div class="field field--name-field-tags field--type-entity-reference field--label-hidden field--items"> <div class="field--item"><a href="/tags/drupal" hreflang="es">Drupal</a></div> <div class="field--item"><a href="/tags/d8" hreflang="es">D8</a></div> </div> <div class="field field--name-body field--type-text-with-summary field--label-hidden field--item"><p>En esta segunda parte de la gestión de la configuración en Drupal 8, vamos a ver cómo podemos mantener diferentes configuraciones para cada entorno.</p> <p>Si te has perdido la <a href="https://www.carloscarrascal.com/blog/gestion-de-la-configuracion-en-drupal-8">primera parte de este artículo</a>, en ella vimos los principios básicos para gestionar la configuración de nuestros sitios con Drupal 8, y deberías darle un repaso antes de seguir con este.</p> <p>Antes de empezar vamos a hacer un pequeño ejemplo y explicar cuales son los casos de uso.</p> <h2>El problema</h2> <p>En cualquier proyecto en el que tengamos varios entornos (integración, desarrollo, producción, etc.) es muy probable que queramos tener configuraciones distintas de nuestro sitio para cada uno de ellos, o varios, o uno solo. Por ejemplo:</p> <ul> <li>Activar módulo <em>Devel</em> en el entorno de desarrollo. Este es el ejemplo típico.</li> <li>Distinta configuración de URLs o IPs de servidores con los que tengamos integraciones. Por ejemplo, podemos tener un servidor <a href="https://es.wikipedia.org/wiki/Protocolo_Ligero_de_Acceso_a_Directorios">LDAP</a> para desarrollo y otro para producción.</li> <li>Configuración específica para producción: caches, módulos de log, varnish, CDNs, etc.</li> </ul> <p>Normalmente esto supone que tendremos un bloque de configuración gordo que será común a todos los entornos, pero también bloques específicos que sólo se aplicarán en algunos de ellos.</p> <p>En principio, podríamos sobreescribir la configuración añadiendo líneas al archivo <em>settings.php</em> de los diferentes entornos que tengamos, así:</p> <pre> $config['system.logging']['error_level'] = 'verbose';</pre> <p>Para casos sencillos y concretos puede ser una solución válida, pero usar <em>$config</em> tiene varios inconvenientes:</p> <ul> <li>No se puede añadir nueva configuración, solo modificar la ya existente.</li> <li>No se puede hacer un <em>unset</em>, no se borrará la configuración.</li> <li>No se pueden activar o desactivar módulos de esta forma.</li> <li>El fichero settings sería complicado de mantener si empezamos a meter morralla.</li> </ul> <p>Se va viendo ya donde está el problema, ¿verdad?</p> <p> </p> <h2>¿Que solución hay?</h2> <p>La gente de <a href="http://nuvole.org/">Nuvole</a> ha estado desarrollando una serie de módulos que nos van a ayudar con este pequeño jaleo. Vamos a ver <em>Config Filter</em>, que va a ser la clave para solucionar el problema que acabamos de ver. La primera versión estable <a href="http://nuvole.org/blog/2017/aug/21/stable-release-config-split-and-config-filter">ha visto la luz</a> hace bien poquito, y tuve la suerte de asistir a la conferencia que dió <a href="https://events.drupal.org/vienna2017/sessions/advanced-configuration-management-config-split-et-al">Fabian Bircher en Drupalcon Viena 2017,</a> porque era exactamente lo que estaba buscando.</p> <p>Usando <em>Config Split</em> vamos a poder agrupar y separar bloques de configuración, que podremos activar o desactivar en cada entorno, así como exportar o importar los ficheros desde directorios diferentes a nuestro <em>sync</em>. Este y otros módulos desarrollados también por <em>Nuvole</em>, necesitan el módulo <em>Config Filter,</em> como una dependencia en la que se basan.</p> <p> </p> <h2>Cómo funciona</h2> <p>Para cada bloque de configuración  (<em>Split</em>) que configuremos vamos a poder indicar un directorio donde se guardarán los archivo de configuración de los componentes de ese <em>Split</em>. Mientras el <em>Split</em> esté activo, la configuración de sus componentes se exportará e importará siempre desde ese directorio.</p> <p>Hay dos modos de funcionamiento para la configuración:</p> <ul> <li><strong>Split completo:</strong> Inicialmente se llamaba <em>Blacklist</em> o lista negra. Los componentes indicados aquí se gestionarán exclusivamente desde el <em>Split</em>, por lo que sus archivos de configuración se eliminarán del directorio <em>Sync</em> principal. En este modo podremos incluir módulos completos o bien componentes concretos de esos módulos.</li> <li><strong>Split condicional:</strong> Llamado inicialmente <em>Graylist</em>, con este modo la configuración de sus componentes no se elimina del <em>Sync</em> global, pero mientras el Split este activo, se importará y exportará al Split. Esto nos permite tener configuraciones alternativas por entorno. En este modo solo se nos permite incluir componentes específicos.</li> </ul> <p>Además, para cada <em>Split</em> podremos definir un peso, de forma que si varios <em>Splits</em> definen la misma configuración, el peso marcará cual tiene prioridad.</p> <p><em>Config Split</em> incorpora dos nuevos comandos de <em>Drush</em> para manejar la configuración, son<em> config-split-import</em> y <em>config-split-export</em>:</p> <pre> drush csim drush csex</pre> <p> </p> <h2>Instalación</h2> <p>Lo primero que necesitamos hacer es instalar y activar los módulos:</p> <pre> drush dl config_filter config_split drush en config_filter config_split </pre> <p>Una vez activados vamos a configurar el <em>Split</em> de la configuración. Vamos al panel de administración, en <em>Inicio</em> »  <em>Administración</em> » <em>Configuración</em> » <em>Development</em> » <em>Sincronizar</em>, donde podemos comenzar a crear grupos.</p> <p><img alt="Listado de Splits" class="img-responsive shadow" data-entity-type="file" data-entity-uuid="c8c3a93f-ea04-44cf-b6fd-c427c646f793" src="/sites/default/files/inline-images/configuration-split-screen1.png" /></p> <p>En la imagen se puede ver un <em>Split</em> configurado que hemos llamado <em>Dev</em>, y que está activo en este entorno. Vamos a hacer el ejemplo sencillo de usar este Split para activar el módulo Devel en el entorno de desarrollo. Veámos la configuración básica de este Split en la siguiente imagen.</p> <p><img alt="Configuración básica del Split" class="img-responsive shadow" data-entity-type="file" data-entity-uuid="4a3badef-147b-4ad4-a40f-fbe79e32a59f" src="/sites/default/files/inline-images/configuration-split-edit-basico.jpg" /></p> <p>Indicamos la ruta física donde se van a guardar los archivos de configuración de este split, relativa a la raíz de Drupal. Le damos un peso e indicamos si está activo o no.</p> <p>A continuación, y para este ejemplo, seleccionamos el módulo Devel en el bloque de Complete Split, para sacar completamente la configuración de este módulo de la general, ya que solamente vamos a querer activarlo en desarrollo.</p> <p><img alt="Configuración de opciones para el Split" class="img-responsive shadow" data-entity-type="file" data-entity-uuid="3f237228-58a6-40a1-bb6b-d347b5890596" src="/sites/default/files/inline-images/configuration-split-edit-opciones.jpg" /></p> <p>Guardamos la configuración del Split, y para aplicarla tenemos que exportar, con Drush <em>config-split-export</em>:</p> <pre> drush csex</pre> <p>Cuando termine el comando, los archivos de configuración de <em>Devel</em> estarán en el directorio del split, y como los pusimos en el modo completo, serán eliminados del directorio <em>Sync</em> general. En mi caso, tengo algo así.</p> <pre> $ ll dev/ total 24 -rw-rw-r-- 1 charles staff 281 7 Oct 15:21 devel.settings.yml -rw-rw-r-- 1 charles staff 279 7 Oct 15:21 devel.toolbar.settings.yml -rw-rw-r-- 1 charles staff 283 7 Oct 15:21 system.menu.devel.yml</pre> <p>Ahora podemos ir a nuestro entorno de producción y desactivamos en nuestro entorno de producción este <em>split</em>, sobreescribiendo la configuración en el archivo <em>settings.php</em> del servidor:</p> <pre> // Desactivar configuracion de split de Dev $config['config_split.config_split.dev']['status'] = FALSE; </pre> <p>Por supuesto que esto podemos hacerlo al revés, configurarlo como desactivado por defecto, y activarlo solamente en nuestro entorno de desarrollo, y en este caso puede que fuese la mejor opción, porque nos ahorraremos tocar nada de configuración en producción.</p> <p> </p> <h2>Flujo de trabajo</h2> <p>Esta es una lista básica de los pasos que vamos a necesitar para trabajar con esta configuración en varios entornos. Espero que ayude a clarificar el proceso.</p> <p>En nuestro entorno local:</p> <ol> <li>Obtener la base de datos de producción y bajarla a nuestro entorno local.</li> <li>Borrar caches (<em>drush cr</em>).</li> <li>Actualizar nuestro repositorio de control de versiones. La forma de hacer esto dependerá mucho de nuestro proyecto, pero básicamente lo que queremos es hacer un <em>git pull</em>.</li> <li>Importar la configuración de Dev que hemos preparado. Al venir de producción ésta BD no tiene esa configuración (<em>drush csim dev</em>).</li> <li>Comprobar si hay algún cambio de configuración que se haya hecho en producción, y si es así exportarlo para no perderlo (<em>drush csex</em>).</li> <li>Guardar estos cambios en el control de versiones (<em>git commit</em> + <em>git push</em>).</li> <li>Trabajar en las modificaciones de configuación que queramos hacer sobre nuestro entorno local.</li> <li>Exportar nuestros cambios (<em>drush csex</em>).</li> <li>Subirlos al control de versiones (<em>git commit</em> + <em>git push</em>).</li> </ol> <p>En el entorno de producción:</p> <ol> <li>Desplegar los cambios en los ficheros de configuración en el entorno de producción. Esto dependerá de la forma en que estemos trabajando. Puede ser tan sencillo como hacer un <em>git pull</em>, o desplegar con un <em>Jenkins</em>.</li> <li>Importar la configuración con los últimos cambios. Si nuestro <em>Config Split</em> de <em>Dev</em> está desactivado en el <em>settings.php</em>, esa configuración no se importará (<em>drush csim</em>).</li> </ol> <p> </p> <h2>Conclusiones</h2> <p><em>Config Split</em> es justo la pieza que me faltaba para poder hacer una gestión medianamente decente de la configuración en Drupal 8 para proyectos grandes, sin usar <em>Features</em>.</p> <p>Hasta ahora me esta funcionando sin problemas y en general me gusta como se configura y trabaja con el. Recomiendo sin duda darle una oportunidad porque los chicos de <em>Nuvole</em> han hecho un gran trabajo,</p> <p> </p> <h2>Referencias</h2> <ol> <li><a href="https://www.drupal.org/docs/8/modules/configuration-split/creating-a-simple-split-configuration-dev-modules-only-in-dev">Creating a simple split configuration: Dev modules only in dev environments</a></li> <li> <p><a href="https://events.drupal.org/vienna2017/sessions/advanced-configuration-management-config-split-et-al">Advanced Configuration Management with Config Split et al.</a> (Drupalcon Viena 2017)</p> </li> <li> <p><a href="http://nuvole.org/blog/2017/aug/21/stable-release-config-split-and-config-filter">http://nuvole.org/blog/2017/aug/21/stable-release-config-split-and-conf…</a></p> </li> </ol> </div> </div> <div class="group-footer"> <section> <h2>Añadir nuevo comentario</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=32&amp;2=comment&amp;3=comment" token="cBy-k65ZpSZzYuUeFvRtIUCCtXu8GaU6qLNQDMe_kRI"></drupal-render-placeholder> </section> </div> </article> Sat, 07 Oct 2017 15:26:51 +0000 root 32 at https://www.carloscarrascal.com