Contacta (+34) 622 045 404

Estudio de desarrollo front-end, maquetación web, diseño y programación WordPress freelance.

Fundado por Alberto Fortes, diseñador y maquetador web HTML5 responsive design, front-end developer JavaScript y programador WordPress freelance a medida desde 2006.

Especializados en outsourcing con importantes clientes finales.

Cómo crear un meta_box con multiples valores (WordPress 3)

Recientemente he rediseñador Piscolabis y para ello he hecho un tema en WordPress 3.2.1 a medida. Entre las mejoras de este nuevo theme a medida está el uso de custom post types para todos los tipos de contenido diferentes del site, lo que convierte a WordPress 3 en un verdadero CMS en el cual cada tipo especial de contenido, con sus tipos especiales de campos, pueden materializarse sin necesidad de plugins, sólo programando sencillas funciones en el theme.

Tengo el objetivo, si el tiempo me lo permite, de crear una serie de post acerca de cómo crear un portfolio a medida con WordPress 3, y en ellos describir paso a paso cómo programé el theme de Piscolabis. No obstante, mientras lo hago o no, uno de los puntos en los que más dificultad encontré, por la falta de tutoriales que realmente funcionaran, fue el hecho de poder crear un campo múltiple en el que guardar las fotos de un proyecto.

El escenario es el siguiente. Tenemos un tipo personalizado de contenido (custom_post_type) creado con register_post_type por ejemplo Portfolio, y dicho tipo de contenido además del título y el editor de WordPress, tiene una serie de campos creados ad hoc para este nuevo tipo de contenido (meta boxes) con la función add_meta_box, como pueden ser, la fecha de publicación del proyecto, la url, el o los clientes (esto se hará con taxonomías relacionadas con este nuevo tpo de contenido), la imagen destacada y por supuesto las imágenes de muestra del trabajo.

Todos los campos que hemos enumerado (título, descripción, fecha, url,  imagen destacado) salvo cliente e imágenes del proyecto son de tipo único, no son múltiples ya que sólo soportan un valor. Como he mencionado los clientes se gestionarían mediante la creación de una taxonomía (ya se vería en otro post), por lo tanto el problema que ocupa este artículo es cómo crear un campo múltiple que soporte un o varios valores.

No es tema dificil, pero tampoco sencillo ya que se va a necesitar una buena combinación de PHP, Codex de WordPres, html y jQuery, y además los tutoriales que encontré por internet fallan bastante. Así pues, como paso previo a cómo crear un campo múltiple de imágenes en WordPress, tenemos el problema más básico de cómo crear simplemente un campo múltiple que guarde los distintos valores en la misma meta_key.

Para mostrar la teoría de la forma más sencilla posible, reduciremos el problema a lo más básico. Vamos a crear un tipo de contenido nuevo llamado test, cuyo contenido consista únicamente en un título y en un campo múltiple llamado campo test (imagen).

 

1. Creamos el tipo de contenido.

En primer lugar creamos en el functions.php

add_action('init', 'test_register', 10, 2);

function test_register() {

    $labels = array(
        'name' => _x('Test', 'post type general name')
    );

    $args = array(
        'labels' => $labels,
        'public' => true,
        'supports' => array('title')
      );

    register_post_type( 'test' , $args );
    // Eliminando problemas con los permalinks. Es bastante común que al crear custom posts una vez ya activados los permalinks en WordPress los mismos de errores al intentar visualizarlos.
    add_action('init', 'test_register');
	flush_rewrite_rules();
}

Lo que hemos hecho es simplemente crear el tipo de contenido test de la forma más básica posible. Con esto ya nos debe aparecer en el menú lateral del panel de administración un nuevo tipo de contenido llamado Test. Si entramso veremso que unicamente tiene el campo título. Vamos ahora a crear el campo múltiple.

2. Creamos las llamadas para crear los nuevos campos (meta boxes).

Para ser ordenados, en una única función que llamamos al iniciar el panel de admistración, haremso todas las llamadas para crear todos los nuevos campos personalizados que tendrá el nuevo tipo de contenido Test.

add_action("admin_init", "admin_initi");

function admin_initi(){
      add_meta_box("ns_test", "campo test", "test_print", "test", "normal", "low");
}

function test_print() {
      global $post;
      $values = get_post_custom_values('ns_test', $post->ID);
  	  $c = count($values);
  	  if ($c==0)
  	  {
  	  	?>
	    	<p><input type="text" style="width:90%"  name="ns_test[<?= $c ?>]" id="ns_test[<?= $c ?>]" value="<?php echo $values[$c]; ?>" /> <a href="#" class="del_ns_test">borrar campo</a></p>
	    <?php
  	  } else {
	  	  for($i=0;$i<=$c-1;$i++) {
	  	  	?>
	    		<p><input type="text" style="width:90%"  name="ns_test[<?= $i ?>]" id="ns_test[<?= $i ?>]" value="<?php echo $values[$i]; ?>" />  <a href="#" class="del_ns_test">borrar campo</a></p>
	    	<?php
    	  }
      }
      if($c==0)
      {
      	$rel = $c+1;
      } else {
      	$rel = $c;
      }
      ?>
      <script type="text/javascript">
      	jQuery(document).ready(function()
      	{
     		jQuery('#add_ns_test').click(function()
      		{
      			var r = jQuery(this).attr('rel');
      			var h = "<p><input type='text' style='width:90%'  name='ns_test["+r+"]' id='ns_test["+r+"]' value='' /> <a href='' class='del_ns_test'>borrar campo</a></p>";
      			jQuery('#ns_tests').append(h);
      			var i;
				i = parseInt(r);
      			jQuery(this).attr('rel', i+1);
      			return false;
      		});
      		jQuery('.del_ns_test').click(function()
      		{
      			jQuery(this).parent('p').remove();
      			return false;
      		});
      	});
      </script>
      <div id="ns_tests"></div>
      <a href="#" id="add_ns_test" rel="<?= $rel ?>">A&ntilde;adir otro campo m&aacute;s</a>
      <?php
}

La primera parte pregunta a la base de datos si tenemso ya valores almacenados en la base de datos y si lso hay los pinta como vales de la caja de texto. ES importante fijarse aquí en dos cosas:

  1. Las cajas de textos al tener la misma meta_key  ser valroes múltiples se deben guardar como array, por tanto el name de dicha caja de texto no será ‘ns_test’ sino ‘ns_test[n]’
  2. Si no hay nada almacenado en la base de datos, al menso me debe pintar una caja (si $c == 0 no el array está vacío), en caso contrario me pinta todos los valores

La segunda parte del código es el javascript que nos va a añadir más cajas de texto cuando queremso añadir más valores al campo test  nos va a eliminar cajas de texto cuando queremos eliminar dichos valores. No tiene más complicación.

3. Guardamos en la base de datos los valores

Y aquí es donde he tenido más dificultad y donde los tutoriales que he visto fallaban y simplemente esquivaban este paso o no funcionaban.
La razón es que la función que se suele usar para guardar datos es update_post_meta, que lo mismo te vale para actualizar valores ya existentes que para crearlos si no existen. Pero cuando llegamos al tema de los campos múltiples, o no van fina o simplemente no funciona. Por tanto debemos usar la función add_post_meta Con la complejidad de que esta función añade valores pero no reemplaza.
Así pues debemos hacer antes un script que borre todos los valores de nuestra meta_key (en este caso ns_test) asociados a este post, y una vez borrados todos, guardamos los valores de nuevo. Y con este rodeo esto funciona a las mil maravillas.
En código esto se traduce en:

add_action('save_post', 'save_detailsi');

function save_detailsi($post_id){
  global $post;
  if ($post->post_type == 'test')
  {
  	if ($parent_id = wp_is_post_revision($post_id)) $post_id = $parent_id;
  	if (!empty($_POST['ns_test']) && is_array($_POST['ns_test']))
  	{
  	 	delete_post_meta($post_id, 'ns_test');
  	 	foreach ($_POST["ns_test"] as $value)
  	 	{
  	 		add_post_meta($post->ID, "ns_test", $value);
  	 	}
  	} else {
  		delete_post_meta($post_id, 'ns_test');
  	}
  }
}

Con esto terminamos el código que nos permite guardar multiples valores de un mismo campo en un custom post type.

El código en su conjunto queda así:

<?php

add_action('init', 'test_register', 10, 2);

function test_register() {

    $labels = array(
        'name' => _x('Test', 'post type general name')
    );

    $args = array(
        'labels' => $labels,
        'public' => true,
        'supports' => array('title')
      );

    register_post_type( 'test' , $args );
    // Eliminando problemas con los permalinks. Es bastante común que al crear custom posts una vez ya activados los permalinks en WordPress los mismos de errores al intentar visualizarlos.
    add_action('init', 'test_register');
	flush_rewrite_rules();
}

add_action("admin_init", "admin_initi");

function admin_initi(){
      add_meta_box("ns_test", "campo test", "test_print", "test", "normal", "low");
}

function test_print() {
      global $post;
      $values = get_post_custom_values('ns_test', $post->ID);
  	  $c = count($values);
  	  if ($c==0)
  	  {
  	  	?>
	    	<p><input type="text" style="width:90%"  name="ns_test[<?= $c ?>]" id="ns_test[<?= $c ?>]" value="<?php echo $values[$c]; ?>" /> <a href="#" class="del_ns_test">borrar campo</a></p>
	    <?php
  	  } else {
	  	  for($i=0;$i<=$c-1;$i++) {
	  	  	?>
	    		<p><input type="text" style="width:90%"  name="ns_test[<?= $i ?>]" id="ns_test[<?= $i ?>]" value="<?php echo $values[$i]; ?>" />  <a href="#" class="del_ns_test">borrar campo</a></p>
	    	<?php
    	  }
      }
      if($c==0)
      {
      	$rel = $c+1;
      } else {
      	$rel = $c;
      }
      ?>
      <script type="text/javascript">
      	jQuery(document).ready(function()
      	{
     		jQuery('#add_ns_test').click(function()
      		{
      			var r = jQuery(this).attr('rel');
      			var h = "<p><input type='text' style='width:90%'  name='ns_test["+r+"]' id='ns_test["+r+"]' value='' /> <a href='' class='del_ns_test'>borrar campo</a></p>";
      			jQuery('#ns_tests').append(h);
      			var i;
				i = parseInt(r);
      			jQuery(this).attr('rel', i+1);
      			return false;
      		});
      		jQuery('.del_ns_test').click(function()
      		{
      			jQuery(this).parent('p').remove();
      			return false;
      		});
      	});
      </script>
      <div id="ns_tests"></div>
      <a href="#" id="add_ns_test" rel="<?= $rel ?>">A&ntilde;adir otro campo m&aacute;s</a>
      <?php
}

add_action('save_post', 'save_detailsi');

function save_detailsi($post_id){
  global $post;
  if ($post->post_type == 'test')
  {
  	if ($parent_id = wp_is_post_revision($post_id)) $post_id = $parent_id;
  	if (!empty($_POST['ns_test']) && is_array($_POST['ns_test']))
  	{
  	 	delete_post_meta($post_id, 'ns_test');
  	 	foreach ($_POST["ns_test"] as $value)
  	 	{
  	 		add_post_meta($post->ID, "ns_test", $value);
  	 	}
  	} else {
  		delete_post_meta($post_id, 'ns_test');
  	}
  }
}

Y es el que funciona en Piscolabis diseño web. Queda pendiente esa serie de post tutoriales de cómo crear un portfolio en WordPress 3.

[Actualización: También puedes ver un caso concreto para mi nuevo portfolio: Crear un sistema de testimonios con Custom Post Types]