Agregar un campo personalizado de tipo galería a tus entradas

En este tutorial vas a ver cómo agregar un campo personalizado de tipo galería a un Custom Post Type de WordPress.

Lo he implementado como un plugin independiente, pero lo normal es que todo el código quede integrado en un plugin propio que implemente más funcionalidades en tu aplicación.

Para crear y editar la galería desde el escritorio usarás la librería de medios de WordPress que te dará el trabajo más complicado hecho: la subida de imágenes, selección, eliminación, etc.

Para almacenar la galería en el campo meta (custom field o campo personalizado), sólo necesitarás guardar los identificadores de las imágenes que hayas seleccionado.

También vas a ver el código para mostrar la galería a los visitantes de la web utilizando un plugin de javascript que implementa la típica caja de luz para ir mostrando las imágenes ampliadas, modo pase diapositivas, índice de miniaturas, etc.

Resumiendo, vas a ir dando los siguientes pasos:

Tienes todo el código del plugin en el siguiente repositorio https://github.com/kungfupress/kfp-galeria desde ahí también puedes bajarlo en formato zip para instalarlo y probarlo. Pero si lo que quieres es aprender te aconsejo seguir el tutorial e ir tecleando el código en tu editor para entender cada paso.

Creando el fichero principal del plugin.

Empieza creando la carpeta del plugin y tecleando el contenido del fichero principal del plugin que recibe el mismo nombre de la carpeta, pero con la extensión php. Yo he utilizado kfp-galeria.

<?php
/**
 * Plugin Name:   KFP Galería
 * Description:   Crear un campo personalizado para mostrar una galería
 * Plugin URI:    https://github.com/kungfupress/kfp-galeria
 * Version:       1.2.0
 * Text Domain:   kfp-galeria
 * Plugin Author: Juanan Ruiz
 * Author URI:    https://kungfupress.com/
 *
 * @package kfp_galeria
 */

defined( 'ABSPATH' ) || die();

define( 'KFP_GALERIA_DIR', plugin_dir_path( __FILE__ ) );
define( 'KFP_GALERIA_PLUGIN_FILE', __FILE__ );
define( 'KFP_GALERIA_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
define( 'KFP_GALERIA_VERSION', '1.2.0' );

require_once KFP_GALERIA_DIR . 'include/create-cpt-viaje.php';
require_once KFP_GALERIA_DIR . 'include/create-metabox.php';
require_once KFP_GALERIA_DIR . 'include/save-custom-field.php';
require_once KFP_GALERIA_DIR . 'include/show-gallery-frontend.php';

De momento lo único que funcionará son los define. Si quieres ir probando el plugin paso a paso ve comentando las líneas con require_once cuyo código no hayas implementado hasta ese paso.

Definiendo el CPT

Crea ahora una carpeta include en el plugin y un nuevo fichero al que llamarás create-cpt-viaje.php (puedes cambiar el nombre si tu CPT va de otra cosa).

<?php
/**
 * File: kfp-galeria/include/create-cpt-viaje.php
 *
 * @package kfp_galeria
 */

defined( 'ABSPATH' ) || die();

add_action( 'init', 'kfp_cpt_viaje', 10 );
/**
 * Crea un CPT Viaje con lo mínimo
 *
 * @return void
 */
function kfp_cpt_viaje() {
	$args = array(
		'public' => true,
		'label'  => 'Viajes',
	);
	register_post_type( 'viaje', $args );
}

En este fichero llamas a la función register_post_type() para que registre un nuevo CPT con los parámetros que has preparado antes en la variable $args, en este caso reducidos a su mínima expresión.

Esta función va dentro de la función kfp_cpt_viaje, que está asociada al hook 'init' de WordPress. Este hook se lanza cuando la mayor parte de WordPress ya se ha cargado pero antes de imprimir ninguna cabecera, este momento es el ideal para que los plugins preparen los elementos que van a necesitar: CPTs, taxonomías, tablas en la base de datos, etc.

Meta box y custom field

La meta box es esa caja con uno o varios campos de formularios que suelen aparecer al final de un post con los campos meta o custom field.

Vas a definirla en un fichero aparte dentro de la carpeta include con el nombre create-metabox.php

<?php
/**
 * File: kfp-galeria/include/create-metabox.php
 *
 * @package kfp_galeria
 */

defined( 'ABSPATH' ) || die();

add_action( 'add_meta_boxes', 'kfp_galeria_register_meta_box' );
/**
 * Registra Meta Box para la galería
 *
 * @return void
 */
function kfp_galeria_register_meta_box() {
	add_meta_box(
		'kfp-galeria',
		'Galería de fotos',
		'kfp_galeria_show_meta_box',
		'viaje',
		'normal',
		'high'
	);
}

/**
 * Muestra el meta box para asociar una galería de imágenes
 * Si la galería ya existe muestra las miniaturas de las imágenes
 * La información que realmente se graba en el campo meta va en el input oculto
 * con name 'galeria'
 *
 * @param Post $post Objeto con la entrada o contenido actual.
 * @return void
 */
function kfp_galeria_show_meta_box( $post ) {
	$galeria = $post->_galeria;
	$html    = '<div id="mb-vista-previa-galeria">';
	if ( ! empty( $galeria ) ) {
		// El campo galería almacena los IDs de las imágenes separados por comas
		// Utiliza explode para obtener un array de IDs.
		$galeria_ids = explode( ',', $galeria );
		foreach ( $galeria_ids as $attachment_id ) {
			$img   = wp_get_attachment_image_src( $attachment_id, 'thumbnail' );
			$html .= '<img class="mb-miniatura-galeria" src="';
			$html .= esc_url( $img[0] ) . '">';
		}
	}
	$html .= '</div>';
	$html .= '<input name="galeria" id="ids_galeria" type="hidden" value="';
	$html .= esc_attr( $galeria ) . '" >';
	$html .= '<div id="mb-botonera-galeria">';
	$html .= '<input id="boton_crear_galeria" class="button" type="button" value="';
	$html .= esc_html__( 'Crear/editar galería', 'kfp-galeria' ) . '" >';
	$html .= '<input id="boton_eliminar_galeria" class="button" type="button" value="';
	$html .= esc_html__( 'Eliminar galería', 'kfp-galeria' ) . '" >';
	$html .= '</div>';
	wp_nonce_field( 'graba_galeria', 'galeria_nonce' );
	echo $html;
}

add_action( 'admin_enqueue_scripts', 'kfp_galeria_admin_scripts' );
/**
 * Agrega los scripts que crean la conexión entre los campos de imagen y galería y el media uploader
 *
 * @return void
 */
function kfp_galeria_admin_scripts() {
	if ( is_admin() ) {
		wp_enqueue_media(); // Carga la API de JavaScript para utilizar wp.media.
		wp_register_script(
			'kfp-galeria-meta-box',
			KFP_GALERIA_PLUGIN_URL . 'js/gallery-meta-box.js',
			array( 'jquery' ),
			KFP_GALERIA_VERSION,
			true
		);
		wp_enqueue_script( 'kfp-galeria-meta-box' );
		wp_enqueue_style(
			'kfp-galeria-admin-css',
			KFP_GALERIA_PLUGIN_URL . 'css/admin.css',
			array(),
			KFP_GALERIA_VERSION
		);
	}
}

En este código primero defines la metabox con:

  • un slug o nombre corto (‘kfp-galeria’),
  • una etiqueta para la caja (‘Galería de fotos’),
  • el nombre de la función que mostrará el metabox al editar el post,
  • una lista con los tipos de contenidos que van a usar el metabox (en este caso sólo ‘viaje’) ,
  • la posición del metabox (‘normal’) y
  • la prioridad de colocación (‘high’).

Luego defines la función kfp_galeria_show_meta_box que mostrará la metabox cuando edites un CPT de tipo viaje. He puesto comentarios en el código para que se entienda lo que va haciendo.

El campo más importante de este formulario es el <input name='galeria' ...> pues es el que realmente almacena el contenido de la galería en la base de datos y asociado a la entrada actual.

Este sería el HTML resultante visto en el navegador con algunas miniaturas cargadas (esto no tienes que teclearlo, es sólo para orientarte).

<input type="hidden" id="galeria_nonce" name="galeria_nonce" value="93b44a495b">
<input type="hidden" name="_wp_http_referer" value="/wp-admin/post.php?post=46&amp;action=edit">
<div id="mb-vista-previa-galeria">
	<img class="mb-miniatura-galeria" src="http://localhost:8888/wp-content/uploads/2020/01/asia-beijing-china-19872-1024px-150x150.jpg">
	<img class="mb-miniatura-galeria" src="http://localhost:8888/wp-content/uploads/2020/01/architecture-asia-asian-236148-1024px-150x150.jpg">
	<img class="mb-miniatura-galeria" src="http://localhost:8888/wp-content/uploads/2020/01/ancient-architecture-asia-734102-1024px-150x150.jpg">
	<img class="mb-miniatura-galeria" src="http://localhost:8888/wp-content/uploads/2020/01/agriculture-asia-countryside-235731-1024px-150x150.jpg">
</div>
<input id="ids_galeria" type="hidden" name="galeria" value="38,39,40,41">
<div id="mb-botonera-galeria">
	<input id="boton_crear_galeria" class="button" type="button" value="Crear/editar galería">
	<input id="boton_eliminar_galeria" class="button" type="button" value="Eliminar galería">
</div>

Llamando al módulo de galerías de WordPress desde JavaScript

Por último registra y encola los ficheros JavaScript y CSS que vas a necesitar. Y aquí es donde viene el meollo de la cuestión, el fichero que hace toda la magia en esta historia js/gallery-meta-box.js

Para el tratamiento de imágenes y galerías WordPress utiliza una librería de JavaScript llamada Backbone, yo no estoy muy puesto en ella así que tampoco puedo explicar bien todo lo que se está haciendo aquí. El plugin WP Media JavaScript Guide documenta mejor todo esto. En cualquier caso tienes el código comentado.

Este script hace en parte algo parecido a lo que se hacía en el fichero create-metabox.php:

  • Cargar en el frame las imágenes que vienen en el campo meta.
  • Si se añade o elimina alguna imagen se modifica de nuevo el código que pinta las miniaturas en el metabox y la lista de identificadores que se almacena en el campo meta oculto.
  • También está el código del botón para eliminar todos las imágenes y dejar la galería a cero.
/**
 * File: kfp-galeria/js/gallery-meta-box.js
 * 
 * https://github.com/ericandrewlewis/wp-media-javascript-guide
 */

jQuery(document).ready(function($){
	var meta_gallery_frame;

	$( '#boton_crear_galeria' ).click(function(e) {
		e.preventDefault();
		// Si el frame existe abre la modal.
        if ( meta_gallery_frame ) {
            meta_gallery_frame.open();
            return;
		}
		// Si no hay valores crea una galería de cero, si los hay edita la actual.
		var ids_galeria = $( '#ids_galeria' ).val();
		if ( !( ids_galeria ) ) {
			// Crea un nuevo frame de tipo galería
			meta_gallery_frame = wp.media.frames.wp_media_frame = wp.media( {
				title: 'Galería de fotos',
				frame: "post",
				state: 'gallery-library',
				library: {
					type: 'image'
				},
				multiple: true
			} );
			// Abre la modal con el frame
			meta_gallery_frame.open();
		} else {
			// Abre la modal con el frame y con los attachment de la galería cargados
			meta_gallery_frame = wp.media.gallery.edit( "" );
		}
		// Cuando se actualice la galería, pulsando el botón correspondiente de la modal, 
		// actualiza las miniaturas y los valores que se guardarán en el input oculto.
		meta_gallery_frame.on("update", function(selection) {
			var $vista_previa = $( '#mb-vista-previa-galeria' )
			$vista_previa.html( '' );
			// La función map itera sobre selection.models, crea el código html y devuelve los ids.
			var ids = selection.models.map(
				function( e ) {
					elemento = e.toJSON();
					imagen_url = typeof elemento.sizes.thumbnail !== 'undefined' ? elemento.sizes.thumbnail.url : elemento.url;
					html = "<div class='mb-miniatura-galeria'><img src='" + imagen_url + "'></div>";
					$vista_previa.append( html );
					return e.id;
				}
			);
			$( '#ids_galeria' ).val( ids.join( ',' ) ).trigger( 'change' );
		});
	});

	$('#boton_eliminar_galeria').click(function(e) {
		e.preventDefault();
		// Elimina los ids del input.
		$( '#ids_galeria' ).val( '' ).trigger( 'change' );
		// Elimina las miniaturas.
		$( '#mb-vista-previa-galeria' ).html( '' );
		return;
	});

});

Agrega también una carpeta css en el plugin y crea un archivo admin.css para darle un poco de estilo a las miniaturas

#mb-vista-previa-galeria {
	padding: 1em;
}
.mb-miniatura-galeria {
	float: left;
	width: 150px;
	height: 150px;
}
#mb-botonera-galeria {
	clear: both;
	padding: 1em;
}

Grabando la galería en la base de datos

El siguiente paso será grabar la galería que has montado con tanto esfuerzo en la base de datos. Lo único que vas a grabar son los IDs (identificadores en la base de datos de WordPress) de las imágenes que has seleccionado y en el orden en que las has colocado. Se guardan como una cadena de texto separada por comas en un campo meta llamado _galeria (el que venía del input oculto del formulario) que quedará asociado al CPT actual.

<?php
/**
 * File: kfp-galeria/include/save-custom-field.php
 *
 * @package kfp_galeria
 */

defined( 'ABSPATH' ) || die();

add_action( 'save_post', 'kfp_galeria_save_custom_field' );
/**
 * Graba los campos personalizados que vienen del formulario de edición del post
 *
 * @param int $post_id Post ID.
 *
 * @return bool|int
 */
function kfp_galeria_save_custom_field( $post_id ) {
	// Comprueba que el nonce es correcto para evitar ataques CSRF.
	if ( ! isset( $_POST['galeria_nonce'] ) || ! wp_verify_nonce( $_POST['galeria_nonce'], 'graba_galeria' ) ) {
		return $post_id;
	}
	// Comprueba que el usuario actual tiene permiso para editar esto
	if ( ! current_user_can( 'edit_post', $post_id ) ) {
		wp_die(
			'<h1>' . __( 'Necesitas más privilegios para publicar contenidos.', 'kfp-galeria' ) . '</h1>' .
			'<p>' . __( 'Lo siento, no puedes crear contenidos desde esta cuenta.', 'kfp-galeria' ) . '</p>',
			403
		);
	}
	// Ahora puedes grabar los datos, sin olvidar el sanitize.
	$galeria = sanitize_text_field( $_POST['galeria'] );
	update_post_meta( $post_id, '_galeria', $galeria );
	
	return true;
}

El hook seleccionado para hacer esta tarea es 'save_post', que se lanza justo después de grabar el resto del contenido de la entrada en la base de datos. La función que asocias a este hook recibe el identificador de la entrada con lo que luego podrás asociar el campo meta _galeria a la entrada actual utilizando la función update_post_meta().

Antes de grabar compruebas que el campo ‘nonce’ que agregaste por seguridad dentro del formulario del metabox es correcto y que el usuario tiene activada la opción de editar contenidos (aunque si estás editando una entrada esto sería un poco redundante nunca está de más comprobar estos detalles).

Antes de seguir verifica que puedes editar la galería y que los datos se graban cuando guardas o actualizas el CPT.

Mostrando la galería en el frontend

Para poner el broche de oro deberás hacer que la galería se muestre a los visitantes del sitio con el resto del contenido del CPT. Crea un archivo en la carpeta include con el nombre show-gallery-frontend.php

Agrega el filter hook 'the_content' para que cuando se cargue el contenido de una entrada verifique si existe el campo meta: _galeria y en ese caso escriba el código para mostrar la galería.

Vas a usar varias funciones interesantes de WordPress:

  • get_post_custom(): devuelve los campos meta asociados a un post.
  • wp_get_attachment_image_src(): devuelve la imagen asociada a un attachment (elemento tipo medio)
  • esc_url(): elimina elementos no permitidos o peligrosos en una URL
<?php
/**
 * File: kfp-recetas/include/show-gallery-frontend.php
 *
 * @package kfp_galeria
 */

defined( 'ABSPATH' ) || die();

add_filter( 'the_content', 'kfp_show_gallery' );
/**
 * Agrega los custom fields al contenido del post
 * Observa que los Custom Fields se devuelven como array (por si hay más de uno)
 * Por ello al llamarlo hay que cargar el primer elemento del array con [0]
 * El parámetro data-fancybox="gallery" en el enlace de cada miniatura
 * es para que el plugin fancybox funcione correctamente. 
 *
 * @param string $content Contenido del post actual.
 * @return string
 */
function kfp_show_gallery( $content ) {
	$custom_fields = get_post_custom();
	if ( isset( $custom_fields['_galeria'] ) ) {
		$galeria_ids = explode( ',', $custom_fields['_galeria'][0] );
		$content    .= '<div id="vista-previa-galeria">';
		foreach ( $galeria_ids as $attachment_id ) {
			$img      = wp_get_attachment_image_src( $attachment_id, 'full' );
			$thumb    = wp_get_attachment_image_src( $attachment_id, 'thumbnail' );
			$content .= '<a data-fancybox="gallery" class="miniatura-galeria" ';
			$content .= 'href="' . esc_url( $img[0] ) . '">';
			$content .= '<img src="' . esc_url( $thumb[0] ) . '"></a>';
		}
		$content .= '</div>';
	}

	return $content;
}

Con esto ya se debería mostrar la galería en tu entrada. Como no lleva ninguna hoja de estilo ni script, es posible que las miniaturas hayan quedado una debajo de otra, comprueba de todas formas que funcionen los enlaces al hacer clic en ellas. Debe cargar la imagen correspondiente pero no aparece ningún elemento de navegación, para volver a la entrada tendrás que usar la función retroceder del navegador.

Asociar a la galería un plugin de JavaScript para mejorar la experiencia de usuario

Para rematar la faena, como te dije al principio, vas a agregar algunos estilos y un script de terceros para conseguir una mejor experiencia de usuario en la galería.

En el mismo archivo show-gallery-frontend.php agrega al final el código para registrar y encolar el código JavaScript y el CSS.

add_action( 'wp_enqueue_scripts', 'kfp_galeria_scripts' );
/**
 * Agrega plugin de jquery para visualizar la galeria con lightbox.
 *
 * @return void
 */
function kfp_galeria_scripts() {
	if ( is_singular( 'viaje' ) ) {
		wp_register_script(
			'kfp-galeria-lightbox',
			KFP_GALERIA_PLUGIN_URL . 'js/jquery.fancybox.min.js',
			array( 'jquery' ),
			KFP_GALERIA_VERSION,
			true
		);
		wp_enqueue_script( 'kfp-galeria-lightbox' );
		wp_register_style(
			'kfp-galeria-lightbox-css',
			KFP_GALERIA_PLUGIN_URL . 'css/jquery.fancybox.min.css',
			null,
			KFP_GALERIA_VERSION
		);
		wp_enqueue_style( 'kfp-galeria-lightbox-css' );
		wp_register_style(
			'kfp-galeria-frontend-css',
			KFP_GALERIA_PLUGIN_URL . 'css/frontend.css',
			null,
			KFP_GALERIA_VERSION
		);
		wp_enqueue_style( 'kfp-galeria-frontend-css' );
	}
}

El código JavaScript y el CSS asociado lo puedes descargar de la web http://fancyapps.com/fancybox/3/

Utiliza el botón Download y descomprime el zip que se descarga. Ve a la carpeta dist y copia de allí los archivos jquery.fancybox.min.js y jquery.fancybox.min.css en las carpetas js y css del plugin respectivamente.

Por último crea un archivo en la carpeta css con nombre frontend.css y el siguiente contenido para que las miniaturas queden alineadas horizontalmente y se ajusten de manera responsiva.

#vista-previa-galeria {
	display:flex;
	flex-direction: row;
	flex-wrap: wrap;
	justify-content: center;
	align-content: center;
	align-items: center;
}

Con esto deberías tener una estupenda galería en tu CPT, 100% integrada con WordPress, editable y personalizable en función de lo que necesite tu proyecto.

Espero que el tutorial te haya resultado interesante. Si encuentras algún fallo o se te ocurre alguna mejora te agradezco que me dejes un comentario.

Referencias

2 comentarios en “Agregar un campo personalizado de tipo galería a tus entradas

    1. Hola, José Ángel:
      Me alegra que te haya gustado y que te haya ‘iluminado’ un poco. Tienes más tutoriales por aquí para seguir practicando.
      Gracias por leerlo y por comentar.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *