Añadir un post desde el frontend con imagen destacada

Esta semana me he encontrado con un problema:
Un cliente nos pidió que sus usuarios de BuddyPress pudiesen publicar post desde el frontend, sin tener que entrar el backend.
Estos post tienen que permitir añadir links, videos e imagen destacada.
Creamos los Custom Post Types (CPT) necesarios y les añadimos un formulario a la página donde se mostrarían los loops de cada CPT para poder añadir post nuevos por los usuarios.
El mayor reto fue poder insertar las imágenes en los post.

Lo primero es crear el formulario con sus campos:

<div class="cpt_form_container">

	<!-- Es IMPORTANTE añadir 'enctype="multipart/form-data"' al formulario para permitir la carga de datos y archivos -->
	<form action="" method="post" id="whats-new-form" name="whats-new-form" enctype="multipart/form-data">

		<div id="whats-new-avatar"> <!-- AVATAR DEL USUARIO -->
			<a href="<?php echo bp_loggedin_user_domain(); ?>">
				<?php bp_loggedin_user_avatar( 'width=' . bp_core_avatar_thumb_width() . '&height=' . bp_core_avatar_thumb_height() ); ?>
			</a> <!-- #whats-new-avatar -->
		</div>

		<div id="whats-new-content">

			<p class="titulo-cpt">
				<?php _e( 'Titula tu asociación', 'ainder' ); ?>
			</p>
			<input type="text" class="bp-suggestions" name="titulo-asociate" id="titulo-cpt" maxlength="60"></input>

			<p class="input-cpt">
				<?php printf( __( '%s, explica en qué consiste y lo que necesitas', 'ainder' ), bp_get_user_firstname( bp_get_loggedin_user_fullname() ) ); ?>
			</p>
			<textarea class="bp-suggestions" name="contenido-asociate" id="whats-new" required maxlength="800"></textarea>

			<p class="input-cpt">
				<?php _e( 'País', 'ainder' ); ?>
			</p>
			<input type="text" class="bp-suggestions" name="pais-asociate" id="pais-cpt" required></input>

			<p class="input-cpt">
				<?php _e( 'Localidad', 'ainder' ); ?>
			</p>
			<input type="text" class="bp-suggestions" name="localidad-asociate" id="localidad-cpt" required></input>

			<p class="input-cpt">
				<?php _e( 'Periodo de máxima vigencia de la oferta', 'ainder' ); ?>
			</p>
			<input type="date" class="bp-suggestions" name="tiempo-asociate" id="tiempo-cpt" min="<?php echo date('Y-m-d') ?>" required></input>

			<p class="input-cpt">
				<?php _e( 'Añade una imagen', 'ainder' ); ?>
			</p>
			<input type="file" class="bp-suggestions fileinputs" name="imagen-asociate" id="imagen-cpt">

			<p class="input-cpt">
				<?php _e( 'Añade un video', 'ainder' ); ?>
			</p>
			<input type="url" class="bp-suggestions" title="Si pegas el enlace para compartir de Youtube, aparecerá el vídeo, si sólo pegas la ruta normal aparecerá el enlace vinculado al vídeo" name="video-asociate" id="video-cpt" cols="50" rows="10">
			
			<p class="input-cpt">
				<?php _e( 'Añade un enlace', 'ainder' ); ?>
			</p>
			<input type="url" class="bp-suggestions" name="enlace-asociate" id="enlace-cpt">
			
			<!-- Boton -->
			<input type="submit" name="submit" id="submit" value="<?php esc_attr_e( 'Publicar', 'ainder' ); ?>" />

			<div id="whats-new-post-in-box">

				<?php _e( 'Publicar en', 'ainder' ); ?>:

				<!-- Aquí creamos un desplegable con un 'select' en el que volcamos las categorías (realmente son taxonomías) del Custom Post Type -->
				<select id="whats-new-post-in" name="categoria-asociate" required>
					<option selected="selected" value="">
					<?php 
						// Creamos los argumentos de la consulta
						$args = array(
							// tipo de post que buscamos (es un Custom Post Type llamado 'asociate')
							'post_type'  => 'asociate',
							// nombre da la taxonomías (en esa taxonomía están guardadaas todas las categorías pertenecientes al Custom POst Type 'asociate')
							'taxonomy'   => 'categoria-asociate',
							// Mostramos todas las categorías, incluuidas las que aún no tengan post publicados
							'hide_empty' => 0,
							// las ordenamos de forma ASCendente
							'order' 	 => 'ASC'
							);

						$categories = get_categories( $args );

						// Recorremos cada una de las categorías del CPT 'asociate' y las imprimimos con un 'echo' en un <option></option>
						foreach( $categories as $category ) { 
							echo "<option value='".$category->name."'>$category->name</option>"; // $category->term_id
						} // end foreach
					?>
				</select>
			</div> <!-- #whats-new-post-in-box -->
		</div><!-- #whats-new-content -->

		<!-- Securizamos un poco el formlario usando la función de WordPress wp_nonce_field() -->
		<?php wp_nonce_field( 'post_update', '_wpnonce_post_update' ); ?>

	</form><!-- #whats-new-form -->
</div> <!-- .cpt_form_container -->

<!-- /FORMULARIO  -->

Después hay que tratar los datos. En este caso los tratamos con PHP en el servidor así:

<?php
// Priemro comprobamos que haya datos
if ( $_POST && ( $_POST['titulo-asociate'] != '' ) ) {

	// Guardamos los datos en variables
	$post_type		= 'asociate';// Qué tipo de post es (en este caso en un CPT llamado 'asociate'. Si fuese un post normal pondríamos 'post' en lugar de la variable $post_type)
	$post_title     = $_POST['titulo-asociate'];
	$post_content   = $_POST['contenido-asociate'];
	$post_category 	= $_POST['categoria-asociate'];
	$video_url	= $_POST['video-asociate'];
	$enlace_url	= $_POST['enlace-asociate'];
	$pais 		= $_POST['pais-asociate'];
	$localidad	= $_POST['localidad-asociate'];
	$fecha_expi 	= $_POST['tiempo-asociate'];

	// Los datos del archivo (imagen) se almacenan en la variable global $_FILES en lugar de en $_POST
	$image		= $_FILES['imagen-asociate'];
	$nombre_archivo = $_FILES['imagen-asociate']['name']; 
	$tipo_archivo 	= $_FILES['imagen-asociate']['type']; 
	$tamano_archivo = $_FILES['imagen-asociate']['size'];

	// Creamos los argumentos del nuevo post que vamos a crear
	$new_post = array(
		// El autor va a ser el usuario actual que está visitando la página
		'post_author'  	=> $user->ID,
		'post_title'   	=> $post_title,
		'post_content' 	=> $post_content,
		'post_category'	=> array($post_category),
		// Lo publicamos directamente (podríamos dejarlo en borrador para revisarlo antes si queremos)
		'post_status'  	=> 'publish',
		// Le dejamos los comentarios abiertos (pordiamos cerrarselos desde aquí)
		'comment_status'=> 'open',
		// Aquí le decimos qué tipo de post es (lo tenemos definido en la primera variable de este script) 
		'post_type'    	=> $post_type
		);

	// Creamos el post con los datos de $new_post y obtenemos su ID nuevecita
	$post_id = wp_insert_post( $new_post );
		
	// Insertamos la "categoría" (realmente es una taxonomía)
	wp_set_object_terms( $post_id, $post_category, 'categoria-asociate');

	if ( !empty( $video_url )  ) {
		// Insertamos en DDBB la URL del video si lo hay
		update_post_meta( $post_id, 'video_url', $video_url );
	}
						
	if ( !empty( $enlace_url ) ) {
		// Insertamos en DDBB la URL del enlace si lo hay
		update_post_meta( $post_id, 'enlace_url', $enlace_url );
	}
				
	if ( !empty( $pais )  ) {
		// Insertamos en DDBB el pais si lo hay
		update_post_meta( $post_id, 'pais', $pais );
	}

	if ( !empty( $localidad )  ) {
		// Insertamos en DDBB la localidad si la hay
		update_post_meta( $post_id, 'localidad', $localidad );
	}

	if ( !empty( $fecha_expi )  ) {
		// Insertamos en DDBB la fecha de expiración del post si la hay
		update_post_meta( $post_id, 'fecha_expiracion', $fecha_expi );
	}

	// PARA LA IMAGEN LO TENEMOS UN POCO MÁS COMPLICADO
	if ( $image['error'] == 0 ) {
		// Comprobamos que es el tipo de archivo que queremos y su tamaño no es excesivo
		if ( !( ( strpos( $tipo_archivo, "gif" ) || strpos( $tipo_archivo, "jpeg" ) || strpos( $tipo_archivo, "png" ) ) && ( $tamano_archivo < 10000000 ) ) ) { 
	   	echo "La extensión o el tamaño de los archivos no es correcta. Póngase en contacto con el administrador.<br><br><table><tr><td><li>Sólo se permiten archivos .jpg, .png o .gif<br><li>Sólo se permiten archivos de 10 Mb máximo.</td></tr></table>";

		} else {
			if ($post_id) {
				_e( '¡Enhorabuena! Publicación correcta', 'ainder' );
				echo "<br>";

			} // end if ($post_id)
		} // end if/else
	} // end if (imagen)

	// Guardamos la ruta del directorio upload en una variable
	$upload_dir = wp_upload_dir();

	// Guardamos la ruta del archivo temporal para cuando lo movamos en una variable
	$uploadfile = $upload_dir["path"] ."/". $_FILES['imagen-asociate']['name'];
 
	if ( move_uploaded_file( $_FILES['imagen-asociate']['tmp_name'], $uploadfile ) ) {
		// Leemos el archivo desde su nueva ubicación y lo guardamos en una variable
		$image_data = file_get_contents( $uploadfile );

		// Nombramos la imagen con el nombre del archivo
		$filename 	= $nombre_archivo;

		if(wp_mkdir_p($upload_dir['path']))
		    $file = $upload_dir['path'] . '/' . $filename;
		else
		    $file = $upload_dir['basedir'] . '/' . $filename;

		// Insertamos los datos de la imagen en la variable
		file_put_contents($file, $image_data);

		// Comprobamos el tipo de archivo a partir de su nombre
		$wp_filetype = wp_check_filetype($filename, null );

		$attachment = array(
		    'post_mime_type' => $wp_filetype['type'],
		    'post_title' 	 => sanitize_file_name($filename),
		    'post_content' 	 => '',
		    'post_status' 	 => 'inherit'
		);

		// Creamos un nuevo post (este es sólo para el archivo. Sí, WordPress crea post para casi todo jeje )
		$attach_id = wp_insert_attachment( $attachment, $file, $post_id );

		// Involucramos al archivo del core de WordPress 'wp-admin/includes/image.php'
		require_once(ABSPATH . 'wp-admin/includes/image.php');

		// Generamos los metadatos de la imagen (si los tiene)
		$attach_data = wp_generate_attachment_metadata( $attach_id, $file );

		// Añadimos dichos metadatos
		wp_update_attachment_metadata( $attach_id, $attach_data );

		// Le asignamos la imagen al post como imagen destacada
		set_post_thumbnail( $post_id, $attach_id );

	} else { 
	   	echo "¡Enhorabuena! Publicación correcta sin imagen"; 
	} // end if/else

} // end if ($_POST)
?>

Como vemos, primero creamos el post y luego le añadimos la imagen.

Espero que le sirva a alguien, porque yo me pasé un tiempo buscando información y probando (perooo…¿no es así como mejor se aprende?).

Pues eso es todo en esta entrada. Espero que la pongáis en práctica y recordad: Sólo se aprende rompiendo y destripando el código, practicando.

Compartidlo si lo veis útil.

9 Comentarios

  1. Yahir McGrady Autor mayo 10, 2016 (2:38 am)

    Hola buen articulo, cuando creo el post, el post recien creado se me duplica dos veces, habra algo que este haciendo mal?

    Responder a Yahir McGrady
    • Jose Lazo Autor mayo 12, 2016 (7:18 pm)

      No sé qué puede estar pasando. Añadiendo «`wp_nonce_field( ‘post_update’, ‘_wpnonce_post_update’ );«` al final del formulario nos aseguramos que sólo se envíe una vez (entre otras cosas).

      Responder a Jose Lazo
  2. Darwin Vivallo Autor noviembre 4, 2018 (1:42 am)

    Bien poco explicado, pero se agradece el código.

    Responder a Darwin Vivallo
  3. Alvaro R Autor julio 7, 2020 (1:34 am)

    Excelente post me ha ayudado mucho incluso en este 2020…

    Ojala existe alguien que pueda ayudarme ya utilice tu codigo. dejando solamente los elementos que utilizaré que son el titulo, subir imagen destacada, categorias y algun otro. En un post normal…Sin embargo todo funciona y se genera correctamente excepto las categorias. En el caso normal en lugar de «taxonomy» para CPT en tipo post estoy utilizando «category» e incluso probe con «post_category» y se me despliegan mis categorias pero al momento de generar el nuevo post no se asigna ninguna de mis categorias al visualizarlo en el loop.

    Saludos y muchas gracias por la ayuda!.

    Responder a Alvaro R
    • Jose Joaquín Lazo León Autor julio 8, 2020 (8:08 pm)

      Hola Álvaro,

      Me alegro que te sirva. El problema de las categorías puede deberse a que la función que utilizamos para crear el post es wp-insert-post() y como argumento para 'post_category' sólo admite un array con los IDs de las categorías como dice la documentación

      Prueba a cambiar en el formulario la parte que muestra las categorías disponibles y pone

      echo "<option value='".$category->name."'>$category->name</option>";

      por esto otro:

      echo "<option value='".$category->term_id."'>$category->name</option>";

      y debería funcionar.

      Un saludo.

      Responder a Jose Joaquín Lazo León
      • Alvaro R Autor julio 13, 2020 (5:17 am)

        Gracias por contestar José, se resolvió satisfactoriamente mi problema revisando el código minuciosamente había olvidado cambiar el nombre de una de las variables de categoría y eso provocaba el error!.

        Existira algun post tuyo en donde expliques cómo agregar el excerpt y las categorías pero desde un checkbox?

        Gracias y un saludo!

        Responder a Alvaro R
        • Jose Joaquín Lazo León Autor julio 20, 2020 (1:19 pm)

          Hola de nuevo, Álvaro,

          Para agregar el excerpt, simplemente recógelo en una variable $_POST más añadiendo un campo al formulario y añades el valor de esa variable al array que utilizas para los valores de la función wp-insert-post() como dice la documentación.

          Para las categorías desde checkbox en lugar de un select, cambia el formato en el formulario y listos.

          Un saludo.

          Responder a Jose Joaquín Lazo León
  4. Alvaro R Autor julio 19, 2020 (4:39 am)

    Hola Jose, yo de nuevo.

    Disculpa me encanta la idea de hacer un formulario y que los usuarios carguen sus propias entradas. pero estoy atorado en espera de una luz tuya como último recurso.

    Como cargar varias taxonomias? y como subir desde el frontend un archivo para que los demas lo descarguen?

    Ojala puedas ayudarme, abusando de tu amabilidad.

    Gracias y saludos
    Alvaro

    Responder a Alvaro R
    • Jose Joaquín Lazo León Autor julio 20, 2020 (1:26 pm)

      Hola, Álvaro,

      Para que los usuarios puedan subir archivos procede igual que para subir imágenes, pero ten mucho cuidado con los formatos que admites por seguridad. Esos archivos se guardarían en la galería de medios de WordPress y podrán ser descargables por los demás usuarios si los muestras en una etiqueta <a download href="url del archivo">Nombre del archivo</a>

      Responder a Jose Joaquín Lazo León

Deje un Comentario

*Campos obligatorios Por favor valide los campos obligatorios

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.