La forma más “limpia” que se me ocurre es creando una acción en el controlador y usando la response para forzar que se descargue.
Por ejemplo algo así, en el datagrid:
‘buttons’=> [
‘listar’=>function ($url,$model){
return Html::a(’’, ['/controlador_que_sea/descarga', 'archivo' => $model->rutaRecurso],
[‘title’=>‘Ver’, ‘target’=>’_blank’]);
},
Supongo que el ‘target’ => ‘blank’ lo podrías quitar.
Luego en tu controlador, escribes una acción parecida a esto. Lo he sacado de la guía de Yii2 y lo he traducido un poco para aplicarlo a tu ejemplo:
public function actionDescarga($archivo)
{
// buscamos el alias que tengas definido con la ruta de los uploads
$rutaArchivos = Yii::getAlias('@web/upload/');
// esto es una comprobación de que el nombre del archivo no tenga cosas raras
// (para no permitir "../" y que se puedan descargar archivos arbitrarios)
if (!preg_match('/^[a-z0-9]+\.[a-z0-9]+$/i', $archivo) || !is_file("$rutaArchivo/$archivo")) {
throw new \yii\web\NotFoundHttpException('Archivo no encontrado.');
}
// si el archivo existe en la ruta correcta, mandamos la respuesta de vuelta
// con sendFile, lo cual forzará la descarga
return Yii::$app->response->sendFile("$rutaArchivo/$archivo", $archivo);
}
Como siempre lo hago de memoria y el código puede tener errores, pero hacerlo así debería forzar siempre la descarga. Además esta forma de hacerlo te permite añadir código al actionDescarga y jugar con otras cosas (como llevar un contador de descargas de cada archivo, guardar el usuario y la fecha del que lo descargue…)
A ver si te sirve.