1. Code
  2. PHP

Adjuntar archivos a tus entradas utilizando metaboxes personalizados de WordPress, Parte 2

En el primer artículo, echamos un vistazo a cómo podíamos adjuntar un archivo, en concreto, un PDF, a las entradas y páginas de WordPress sin tener que utilizar un plugin o una solución de terceros. En este momento, sólo puedes cargar archivos: no hay forma de desactivar el enlace o eliminar el enlace al archivo una vez este ha sido cargado. En este tutorial, echaremos un vistazo a cómo proporcionar un estilo ligeramente mejorado para el enlace de descarga y a cómo ampliar la funcionalidad del metabox personalizado al permitir a los usuarios eliminar archivos después de que haberlos descargado.
Scroll to top

Spanish (Español) translation by Eva Collados Pascual (you can also view the original English article)

En el primer artículo, echamos un vistazo a cómo podíamos adjuntar un archivo, en concreto, un PDF, a las entradas y páginas de WordPress sin tener que utilizar un plugin o una solución de terceros. En este momento, sólo puedes cargar archivos: no hay forma de desactivar el enlace o eliminar el enlace al archivo una vez este ha sido cargado. En este tutorial, echaremos un vistazo a cómo proporcionar un estilo ligeramente mejorado para el enlace de descarga y a cómo ampliar la funcionalidad del metabox personalizado al permitir a los usuarios eliminar archivos después de que haberlos descargado.


Vamos a vestirlo

Si has seguido el código del primer tutorial, deberías tener una demostración funcional de cómo adjuntar un PDF a una entrada (o página) de WordPress; sin embargo, la presentación general no tiene muy buen aspecto:

Antes de ir más allá, vamos a limpiarlo un poco para que se vea un algo más integrado con el tema predeterminado. Recuerda que estamos usando Twentyeleven como nuestro tema predeterminado de manera que todos estemos en la misma página mientras trabajamos en este tutorial.

En primer lugar, vamos a mover el enlace de descarga a una ubicación más lógica. Si has estado siguiendo los pasos desde el primer tutorial, recordarás que colocamos el enlace de descarga en el archivo single.php que se encuentra en la raíz del directorio del tema. Abre el archivo y localiza el bloque de código que tiene este aspecto:

1
get_template_part( 'content', 'single' );
2
$doc = get_post_meta(get_the_ID(), 'wp_custom_attachment', true);
1
	<a href="	<?php echo $doc['url']; ?>">
2
	Download PDF Here
3
	</a>

Copia todo excepto la primera línea de código y elimínala del archivo. Esto debería dejar sólo una invocación a get_template_part.

A continuación, localiza custom-single.php. Se trata de un archivo de plantilla ubicado en la raíz del directorio del tema. Encuentra la llamada al contenido, the_content(), y después pega el código que acabas de copiar directamente bajo ella. Esto debería dar lugar al siguiente bloque de código:

1
the_content();
2
$doc = get_post_meta(get_the_ID(), 'wp_custom_attachment', true);
3
	<a href="	<?php echo $doc['url']; ?>">
4
	Download PDF Here
5
	</a>

Ahora, vamos a envolver el enlace en un contenedor para que podamos aplicarle fácilmente un estilo. Asignaré a mi contenedor el ID 'wp_custom_attachment'. Siéntete libre de usar lo que quieras, simplemente recuerda referenciarlo correctamente en tu hoja de estilo.

Aquí tienes mi marcado:

1
the_content();
2
$doc = get_post_meta(get_the_ID(), 'wp_custom_attachment', true);
3
<div id="wp_custom_attachment">
4
	<a href="<?php echo $doc['url']; ?>">
5
		Download PDF Here
6
	</a> 
7
</div><!-- #wp_custom_attachment -->

Y aquí está mi CSS:

1
.wp_custom_attachment { 
2
	margin: 8px 0 8px 0;
3
	border: 1px solid #DDD;
4
	background: #EEE;
5
	padding: 8px;
6
	text-align: center;
7
	border-radius: 4px;
8
}

Asumiendo que hayas escrito todo correctamente, el enlace de descarga debería tener este aspecto:

Mucho mejor, ¿verdad? Ahora está diseñado de tal manera que parece estar mejor integrado con el tema. Recuerda hacer los cambios correspondientes en page.php, también (considerando que estamos dando soporte a archivos adjuntos tanto en entradas como en páginas).


Descargar, cuando esté disponible

Antes de continuar, tenemos que hacer un pequeño cambio más en el código que acabamos de añadir. En este momento, el enlace de descarga se muestra sin condiciones. Esto significa que, con independencia de que una entrada tenga o no un archivo, estaremos mostrando su enlace de descarga.

Idealmente, sólo queremos mostrar el enlace de descarga cada vez que haya un archivo para descargar. Por tanto, necesitaremos un condicional. Concretamente, necesitaremos obtener los metadatos asociados a la entrada, determinaremos si existe realmente una dirección URL para el archivo adjunto. Si es así, mostraremos el enlace; de lo contrario, no lo haremos.

En el bloque de código que acabamos de añadir a content-single.php, coloca una instrucción if de apertura justo encima de la etiqueta de apertura wp_custom_attachment y cierra la instrucción justo bajo la etiqueta de cierre del contenedor. En este momento, el código debería tener un aspecto similar al siguiente:

1
the_content();
2
$doc = get_post_meta(get_the_ID(), 'wp_custom_attachment', true);
3
if() { 
4
	<div id="wp_custom_attachment">
5
		<a href="<?php echo $doc['url']; ?>">
6
			Download PDF Here
7
		</a> 
8
	</div><!-- #wp_custom_attachment -->

9
} // end if

Necesitamos comprobar la presencia de la URL del documento. Hay varias maneras de hacerlo, pero normalmente lo compruebo echando un vistazo a su atributo de dirección URL. El bloque de código resultante es:

1
the_content();
2
$doc = get_post_meta(get_the_ID(), 'wp_custom_attachment', true);
3
if(strlen(trim($doc['url'])) > 0) { 
4
	<div id="wp_custom_attachment">
5
		<a href="<?php echo $doc['url']; ?>">
6
			Download PDF Here
7
		</a> 
8
	</div><!-- #wp_custom_attachment -->

9
} // end if

Ahora, el enlace de descarga sólo debería mostrarse cuando haya un archivo adjunto válido que ofrecer en la entrada o página. Pruébalo.


Eliminar el archivo

En este momento, hemos atado algunos cabos sueltos de la anterior entrada y estamos preparados para terminar la funcionalidad necesaria para la eliminación de archivos adjuntos.

Establecer las bases

Recuerdas que en el último tutorial una vez que el usuario intentaba cargar un archivo, teníamos una función de serialización que se activaba y que era responsable de almacenar realmente el archivo en el disco del usuario. Como referencia, esto tiene lugar en save_custom_meta_data.

Para eliminar correctamente un archivo, necesitamos realizar un seguimiento de la ubicación del archivo existente y si un usuario realmente ha solicitado la eliminación del archivo. Lo haremos con una combinación de una caja de entrada y un ancla.

En primer lugar, localiza la función 'wp_custom_attachment' que creamos en la última entrada. Aquí es donde añadimos el elemento de entrada del archivo. Justo debajo del elemento de entrada añade el siguiente código (la función completa se proporcionará a continuación):

1
// Create the input box and set the file's URL as the text element's value

2
$html .= '<input type="text" id="wp_custom_attachment_url" name="wp_custom_attachment_url" value=" ' . $doc['url'] . '" size="30" />';

Esto añadirá un elemento de entrada de texto que realiza un seguimiento del valor de la dirección URL del documento cargado. Vamos a limpiar esto un poco más adelante en el tutorial, pero por ahora, vamos a introducir un ancla para eliminar el archivo. Justo bajo las dos líneas de código que acabamos de añadir, escribe lo siguiente:

1
$html .= '<a href="javascript:;" id="wp_custom_attachment_delete">' . __('Delete File') . '</a>';

Toma nota: habíamos asignado al ancla un ID único. Esto será necesario cuando comencemos a conectar el área de administración para controlar los eventos de usuario. Si no le das a tu ancla este ID, toma nota de lo que le asignes.

Aún no hemos terminado. ¿Recuerdas cómo configuramos la visualización de una sola entrada y página para mostrar condicionalmente el enlace de descarga? Tenemos que hacer lo mismo para el enlace de eliminación. Específicamente, necesitamos mostrar solamente el enlace para eliminar si existe un documento. Por lo tanto, de manera similar, envuelve el delimitador en una instrucción condicional que compruebe la presencia de la dirección URL del documento:

1
// Display the 'Delete' option if a URL to a file exists

2
if(strlen(trim($doc['url'])) > 0) {
3
	$html .= '<a href="javascript:;" id="wp_custom_attachment_delete">' . __('Delete File') . '</a>';
4
} // end if

No es nada demasiado complicado, ¿verdad? Para terminarlo, aquí tienes la función completa tal como está:

1
function wp_custom_attachment() {
2
3
	wp_nonce_field(plugin_basename(__FILE__), 'wp_custom_attachment_nonce');
4
	
5
	$html = '<p class="description">';
6
		$html .= 'Upload your PDF here.';
7
	$html .= '</p>';
8
	$html .= '<input type="file" id="wp_custom_attachment" name="wp_custom_attachment" value="" size="25" />';
9
	
10
	// Grab the array of file information currently associated with the post

11
	$doc = get_post_meta(get_the_ID(), 'wp_custom_attachment', true);
12
	
13
	// Create the input box and set the file's URL as the text element's value

14
	$html .= '<input type="text" id="wp_custom_attachment_url" name="wp_custom_attachment_url" value=" ' . $doc['url'] . '" size="30" />';
15
	
16
	// Display the 'Delete' option if a URL to a file exists

17
	if(strlen(trim($doc['url'])) > 0) {
18
		$html .= '<a href="javascript:;" id="wp_custom_attachment_delete">' . __('Delete File') . '</a>';
19
	} // end if

20
	
21
	echo $html;
22
23
} // end wp_custom_attachment

Volveremos a visitar esta función más adelante en el tutorial, pero pruébala de momento. En primer lugar, dirígete a una entrada que no tenga datos adjuntos. Deberías ver el cuadro de entrada del archivo y un cuadro de entrada vacío. Después de cargar un archivo, deberías ver que el cuadro de entrada contiene la dirección URL del archivo seguida de un enlace para eliminarlo.

Pero aún no hemos terminado. Después de todo, el enlace de eliminación no hace nada.

Conectarlo todo

A continuación, busca el directorio 'js' en la raíz del tema. Añade un nuevo archivo llamado custom_attachment.js. Escribiremos código para esto en un momento, pero el propósito del archivo será lo que nos permita eliminar realmente el PDF que hemos adjuntado a una entrada.

Después de eso, abre el archivo functions.php y añade la siguiente función al final del mismo:

1
function add_custom_attachment_script() {
2
3
	wp_register_script('custom-attachment-script', get_stylesheet_directory_uri() . '/js/custom_attachment.js');
4
	wp_enqueue_script('custom-attachment-script');
5
6
} // end add_custom_attachment_script

7
add_action('admin_enqueue_scripts', 'add_custom_attachment_script');

Esta función leerá el archivo JavaScript que acabamos de crear y lo incluirá en cualquier página de administración del backend de WordPress. Encolar y registrar scripts está más allá del ámbito de este tutorial, pero te recomiendo leer sobre el tema.

A continuación, revisemos el archivo JavaScript. En términos generales, el código debe hacer lo siguiente:

  • Determinar si el enlace de eliminación está presente
  • Si el enlace está presente, adjuntar un controlador de eventos personalizado que borre la entrada de texto que contiene la dirección URL del archivo
  • Ocultar el enlace una vez que el archivo haya sido marcado para su eliminación

A continuación tienes el código fuente que ha sido completamente comentado para explicar qué está haciendo cada línea:

1
jQuery(function($) {
2
3
	// Check to see if the 'Delete File' link exists on the page...

4
	if($('a#wp_custom_attachment_delete').length === 1) {
5
6
		// Since the link exists, we need to handle the case when the user clicks on it...

7
		$('#wp_custom_attachment_delete').click(function(evt) {
8
		
9
			// We don't want the link to remove us from the current page

10
			// so we're going to stop it's normal behavior.

11
			evt.preventDefault();
12
			
13
			// Find the text input element that stores the path to the file

14
			// and clear it's value.

15
			$('#wp_custom_attachment_url').val('');
16
			
17
			// Hide this link so users can't click on it multiple times

18
			$(this).hide();
19
		
20
		});
21
	
22
	} // end if

23
24
});

En este momento, el archivo no se habrá eliminado, pero deberías tener una vista funcional. Busca una página que tenga un archivo adjunto. Tu metabox personalizado debe tener un aspecto similar al siguiente:

Después de hacer clic en el ancla 'Eliminar enlace', el metabox personalizado debe tener este aspecto:

Si no es así, comprueba bien la consola de depuración para asegurarte de que no hay ningún error de JavaScript.


Eliminar el archivo

En este punto, hemos hecho de todo menos eliminar realmente el archivo. Para ello, tendremos que actualizar la función save_custom_meta_data que escribimos en el primer tutorial. Recuerda que la función incluye una comprobación condicional del contenido de la extracción $_FILES procedente de la solicitud POST. Si se rellena la extracción, serializamos el archivo.

Dado que estamos intentando eliminar el archivo, la recopilación $_FILES no debe contener ningún dato, por lo que todo nuestro código deberá estar contenido en una cláusula "else". El código fuente completo para la función se proporcionará a continuación, pero aquí tienes cómo debería ser:

  • Comprobar si hay un documento asociado con la entrada
  • Comprobar si el cuadro de texto utilizado para realizar el seguimiento de la URL del archivo está vacío
  • Si existe un archivo y el cuadro de texto está vacío, eliminar el archivo y actualizar los metadatos asociados

Esto debería ser sencillo: hemos dado a cada entrada un elemento de texto que contiene la dirección URL del archivo. Si la URL del archivo está vacía, significa que el usuario ha hecho clic en el enlace 'Eliminar archivo' y está solicitando su eliminación. Así es cómo podemos lograr justo esto:

1
// Grab a reference to the file associated with this post

2
$doc = get_post_meta($id, 'wp_custom_attachment', true);
3
4
// Grab the value for the URL to the file stored in the text element

5
$delete_flag = get_post_meta($id, 'wp_custom_attachment_url', true);
6
7
// Determine if a file is associated with this post and if the delete flag has been set (by clearing out the input box)

8
if(strlen(trim($doc['url'])) > 0 && strlen(trim($delete_flag)) == 0) {
9
10
	// Attempt to remove the file. If deleting it fails, print a WordPress error.

11
	if(unlink($doc['file'])) {
12
		
13
		// Delete succeeded so reset the WordPress meta data

14
		update_post_meta($id, 'wp_custom_attachment', null);
15
		update_post_meta($id, 'wp_custom_attachment_url', '');
16
		
17
	} else {
18
		wp_die('There was an error trying to delete your file.');
19
	} // end if/el;se

20
	
21
} // end if

Ten en cuenta que, una vez eliminado el archivo, también tenemos que actualizar los metadatos de la entrada vaciando el valor del archivo adjunto, así como el valor de la URL del mismo. En el caso extraño de que el archivo no se eliminase, estamos mostrando un sencillo mensaje de error. El control avanzado de errores está fuera del alcance de este tutorial.

Como prometí, aquí tienes la función de serialización completa:

1
function save_custom_meta_data($id) {
2
3
	/* --- security verification --- */
4
	if(!wp_verify_nonce($_POST['wp_custom_attachment_nonce'], plugin_basename(__FILE__))) {
5
	  return $id;
6
	} // end if

7
	  
8
	if(defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
9
	  return $id;
10
	} // end if

11
	  
12
	if(!current_user_can('edit_page', $id)) {
13
		return $id;
14
   	} // end if

15
	/* - end security verification - */
16
	
17
	// Make sure the file array isn't empty

18
	if(!empty($_FILES['wp_custom_attachment']['name'])) {
19
		
20
		// Setup the array of supported file types. In this case, it's just PDF.

21
		$supported_types = array('application/pdf');
22
		
23
		// Get the file type of the upload

24
		$arr_file_type = wp_check_filetype(basename($_FILES['wp_custom_attachment']['name']));
25
		$uploaded_type = $arr_file_type['type'];
26
		
27
		// Check if the type is supported. If not, throw an error.

28
		if(in_array($uploaded_type, $supported_types)) {
29
30
			// Use the WordPress API to upload the file

31
			$upload = wp_upload_bits($_FILES['wp_custom_attachment']['name'], null, file_get_contents($_FILES['wp_custom_attachment']['tmp_name']));
32
	
33
			if(isset($upload['error']) && $upload['error'] != 0) {
34
				wp_die('There was an error uploading your file. The error is: ' . $upload['error']);
35
			} else {
36
				add_post_meta($id, 'wp_custom_attachment', $upload);
37
				update_post_meta($id, 'wp_custom_attachment', $upload);		
38
			} // end if/else

39
40
		} else {
41
			wp_die("The file type that you've uploaded is not a PDF.");
42
		} // end if/else

43
		
44
	} else {
45
46
		// Grab a reference to the file associated with this post

47
		$doc = get_post_meta($id, 'wp_custom_attachment', true);
48
		
49
		// Grab the value for the URL to the file stored in the text element

50
		$delete_flag = get_post_meta($id, 'wp_custom_attachment_url', true);
51
		
52
		// Determine if a file is associated with this post and if the delete flag has been set (by clearing out the input box)

53
		if(strlen(trim($doc['url'])) > 0 && strlen(trim($delete_flag)) == 0) {
54
		
55
			// Attempt to remove the file. If deleting it fails, print a WordPress error.

56
			if(unlink($doc['file'])) {
57
				
58
				// Delete succeeded so reset the WordPress meta data

59
				update_post_meta($id, 'wp_custom_attachment', null);
60
				update_post_meta($id, 'wp_custom_attachment_url', '');
61
				
62
			} else {
63
				wp_die('There was an error trying to delete your file.');
64
			} // end if/el;se

65
			
66
		} // end if

67
68
	} // end if/else

69
	
70
} // end save_custom_meta_data

71
add_action('save_post', 'save_custom_meta_data');

Por ahora, tienes un metabox personalizado totalmente funcional. Pruébalo.


Haciendo limpieza

Tenemos que hacer un último cambio menor solo para completar nuestra interfaz de usuario. ¿Recuerdas la entrada de texto que añadimos anteriormente en el tutorial que es responsable de mantener la dirección URL del archivo? Podemos marcarla como oculta, no hay razón por la que el usuario necesite verla. El origen de JavaScript seguirá utilizándola correctamente y su valor se leerá en la función de serialización.

La función wp_custom_attachment final debe tener este aspecto:

1
function wp_custom_attachment() {
2
3
	wp_nonce_field(plugin_basename(__FILE__), 'wp_custom_attachment_nonce');
4
	
5
	$html = '<p class="description">';
6
		$html .= 'Upload your PDF here.';
7
	$html .= '</p>';
8
	$html .= '<input type="file" id="wp_custom_attachment" name="wp_custom_attachment" value="" size="25" />';
9
	
10
	// Grab the array of file information currently associated with the post

11
	$doc = get_post_meta(get_the_ID(), 'wp_custom_attachment', true);
12
	
13
	// Create the input box and set the file's URL as the text element's value

14
	$html .= '<input type="hidden" id="wp_custom_attachment_url" name="wp_custom_attachment_url" value=" ' . $doc['url'] . '" size="30" />';
15
	
16
	// Display the 'Delete' option if a URL to a file exists

17
	if(strlen(trim($doc['url'])) > 0) {
18
		$html .= '<a href="javascript:;" id="wp_custom_attachment_delete">' . __('Delete File') . '</a>';
19
	} // end if

20
	
21
	echo $html;
22
23
} // end wp_custom_attachment

Estos dos tutoriales cubrieron mucha información. Hay una serie de soluciones enlatadas, ya sea en forma de plugins, temas u otros complementos, disponibles para integrar funciones como esta, pero parte de lo que constituye ser un buen desarrollador es saber cuándo usar una solución de terceros y cuándo lanzar una propia.

Además, si estás trabajando con WordPress en una capacidad profesional, entonces es importante entender la API. Esperemos que esta serie haya ayudado a mostrar gran parte de lo que se puede hacer aprovechando la funcionalidad básica de WordPress.