Link de descarga de archivos privados o temporales

Buenas drupaleros y bienvenidos a otro tutorial. En esta ocasión os explicaremos como poder crear un "sistema" para poder descargar archivos dentro de la carpeta privada o temporal.

El problema

Seguro que algunos de vosotros habéis usado alguna vez la carpeta privada o la careta temporal para alojar vuestros archivos. Sí, no solo de la carpeta publica vive el hombre! en ocasiones, generalmente por motivos de seguridad, necesitamos ocultar ciertos assets del usuario anónimo o general.

El problema viene cuando intentáis crear una enlace de descarga de alguno de esos archivos y siempre que tengáis bien configurado vuestro drupal, os debería de salir un mensaje de error de permisos o simplemente os da error al descargar el archivo.

Esto se debe a que drupal por defecto bloquea todo aquello que este dentro de la carpeta privada o temporal. A efectos prácticos estas dos carpetas tienen el mismo comportamiento con la única diferencia de que la carpeta temporal borra todo su contenido al ejecutarse el cron. Esto hace que a nivel conceptual se usen para diferentes comportamientos.

El hook responsable

En drupal, tanto en la versión 7 como la versión 8, existe un hook llamado "hook_file_download" el cual nos permite configurar este comportamiento y en este tutorial os voy a explicar como poder configurarlo, dado que la documentación oficial es un poco pobre al respecto.

Para ello, vamos a crear un nuevo permiso para poder controlar que usuarios pueden descargar estos archivos, En caso que queráis que cualquier persona se pueda descargar estos archivos, deberíais de plantearos si la carpeta privada es el lugar correcto.

Creando permisos

/* D7 ejemplo */
/**
 * Implements hook_permission();
 */
function nombre_del_modulo_permission() {
  return array(
    '[nombre de nuestro permiso]' => array(
      'title' => t('[EL titlo que aparecera en el apartado de permisos en la pagina de roles]'),
    ),
  );
}

No voy a entrar al detalle de como crear un nuevo permiso ya que no es el objetivo de este tutorial, simplemente recordad cambiar el texto "[..]" por el que se ajuste y sea claro en vuestro proyecto. 

Recordad que en Drupal 8 los permisos se configuran desde el *.permissions.yml de vuestro modulo.

Configurando el hook_file_download

/* D7 ejemplo */
/**
 * Implements hook_file_download().
 */
function nombre_del_modulo_file_download($uri) {
  $scheme = file_uri_scheme($uri); // Esta funcion esta deprecated para drupal 9 asi que ojo al usarla, para D8 recomiendo \Drupal\Core\File\FileSystem::uriScheme().
  $target = file_uri_target($uri);
  if ($scheme == 'temporary' && strpos($target, $folder_name ) && user_access('[nombre de nuestro permiso]') !== false ) {
    $info = file_get_mimetype($uri); return array('Content-Type' => $info['mime_type']); 
  } 
}
Como vemos el en código anterior lo que hacemos es básicamente comprobar varios puntos:

1. Comprobamos que el archivo este dentro de la carpeta que realmente queremos usar, podríamos usar "temporary" o "private" para la carpeta temporal y privada respectivamente.

2. Como norma general no deberíamos guardar todos los archivos dentro de la raíz de la carpeta temporal o privada yo os recomiendo crear una subcarpeta para cada tipo de archivo para poder estar organizado. De esta forma podremos filtrar también la carpeta donde se encuentra el archivo.

3. Comprobamos que el usuario que esta intentado acceder tiene permisos suficientes para poder descargar el archivo.

Para que os hagáis una idea si hiciéramos un dump de $uri nos devolvería algo como "temporary://nombre_carpeta/nombre_archivo.extension"

Nota: En caso de que el archivo sea una imagen podemos usar  "$info = image_get_info($uri);"

Creando el enlace de descarga

Este un ejemplo de muchos que podréis encontrar por internet, a mi personalmente me funciona muy bien y me gusta dejar a drupal que haga todo el trabajo.

$url = file_create_url($uri);
$options_download['attributes']['title'] = $nombre_del_archivo;
$options_download['attributes']['download'] = '';
$output = '<div><span class="file-download">' . l($nombre_del_archivo, $url, $options_download) . '</span></div>';

Este output nos generara un enlace listo para descargar nuestro archivo deseado.

Conclusión

Como veis, aunque no es algo sencillo, tampoco es un drama una vez entiendes la idea. He de decir que existen módulos contribuidos que hacen algo parecido a esto, incluso he encontrado alguno que otro que incluso permite gestionar permisos. 

Eso esta bien dado que usar módulos contribuidos es lo que "vende" drupal, pero de tanto en tanto es bueno bajar al código y poder hacer nuestro propio código y entender que es lo que pasa por debajo de drupal.

Espero que os haya gustado y ya sabéis compartir y comentar!

Total de votos: 40

Entradas relacionadas

Comentarios (4)

  • Imagen de Arlyns

    Arlyns (no verificado)

    Saludos, llego a este sito despues de 2 dias de intensa busqueda sobre donde se administran los archivos subidos en Drupal. El problema es el siguiente: creo una carpeta de acceso publico y en ella guardo varios archivos pdf para su descarga, hasta alli todo bien y las personas pueden descargar sin problemas, el asunto se complica cuando en otra pagina del sitio necesito colocar un link de descarga de algunos de estos archivos. Drupal vuelve a subir el archivo de origen, por ejemplo, en la pagina 1 estan los enlaces de descarga de archivo1.pdf, archivo2.pdf y archivo3.pdf; al colocar un link de descarga en la pagina 5 del sitio para que el usuario descarge el archivo1.pdf, Drupal vuelve a subir a dicha carpeta puclica el archivo1_0.pdf. Esto me genera que tengo el mismo archivo dos veces en la misma carpeta, uno llamado archivo1.pf y otro llamado archivo1_0.pdf. La pregunta seria como indicarle a Drupal que no suba ese archivo, que use uno que ya esta en el servidor. Espero haber sido lo suficientemente claro y me puedas ayudar. Gracias.

  • Imagen de admin

    admin

    Buenas, la verdad es que no me queda nada claro que estas haciendo. Me gustaria saber como pones ese enlace para la descarga... porque creo que no lo estas poniendo bien...

  • Imagen de Arlyns

    Arlyns (no verificado)

    Ante todo muchas gracias por responder. Intentare ser mas preciso: Cree una contenido de tipo Basic Page llamada Pagina1 (es un ejemplo), le coloque su Body y despues subi varios archivos *.pdf, todo a traves de la gestion de Drupal. Hasta alli no habia problemas, tengo la pagina y los usuarios al navegar en ella pueden descargar los archivos sin problemas. Incluso estos archivos los tengo en una carpeta publica aparte para una mejor gestion de mi parte. Al pasar los años tuve que crear nuevas paginas, asi que cree nuevo contenido de tipo Basic Page y les coloque su Body, pero necesitaba tambien que el visitante pudiera descargar algunos de los archivos ya guardados y que podian descargar en la Pagina 1 (la primera que cree en este ejemplo), al momento de seleccionar el archivo que necesitaba y presiona Subir me di cuenta que Drupal vuelve a subir el archivo a dicha carpeta y como ya existe le agrega un identificador siguiente. Realmente funciona porque se pueden descargar sin problemas, pero el punto es que me duplicara la cantidad de archivos que tengo en el servidor y no es algo que me agrada mucho, por no ser muy eficiente. Ah se me olvida decirte que uso Drupal 7. Espero haber sido mas claro.

  • Imagen de admin

    admin

    Claro, el problema que tienes es que estas intentando subir el mismo archivo y drupal lo interpreta como uno nuevo, solo que al coincidir en nombre, drupal lo renombra a _1,_2... si lo que quieres es reutilizarlo, tienes dos opciones... usar media (https://www.drupal.org/project/media) o puede ponerlo directamente en el body con html (https://www.drupal.org/project/IMCE). Si no.. no se me ocurre otra forma que puedes hacer para reutilizar los archivos ya subidos. Saludos.

Deja un comentario