Introduction
Hello, I am a french student in trainee at an association, and I must create and manage a Nextcloud server.
My clients wanted can download a contact in CSV format. So we want a button to save a contact in CSV file and that app make download this.
Problems
My problem, is I can’t make download the CSV file !
When I click to the download button I have this error in log :
Cannot modify header information - headers already sent by (output started at /var/www/html/nextcloud/apps/lauruxcontact/lib/Controller/ShowcontactController.php:148) at /var/www/html/nextcloud/lib/private/AppFramework/Http/Output.php#69
The code
The function saveInCSV in PHP
public function saveInCSV($utilisateur)
{
$contact = $this->contactsManager->search($utilisateur, ['UID'], ['types' => true])[0];
$filename = "contact_export_" . date("Y-m-d") . ".csv";
// disable caching
$now = gmdate("D, d M Y H:i:s");
header("Cache-Control: max-age=0, no-cache, must-revalidate, proxy-revalidate");
header("Last-Modified: ".$now." GMT");
// force download
header("Content-Type: application/force-download");
header("Content-Type: application/octet-stream");
header("Content-Type: application/download");
// header("Content-type: text/csv");
// disposition / encoding on response body
header("Content-Disposition: attachment;filename=".$filename);
header("Content-Transfer-Encoding: binary");
$data = array(
array("prenom", explode(" ", $contact['FN'])[1])
);
ob_start();
$df = fopen($filename, 'w');
fputcsv($df, array_keys(reset($data)));
foreach ($data as $row) {
fputcsv($df, $row);
}
fclose($df);
ob_get_clean();
readfile($filename); // <= Line where is error
}
The JS code for button
(function(){
if(!OCA.LauruxContact) {
/**
* Namespace for the files app
* @namespace OCA.LauruxContact
*/
OCA.LauruxContact = {};
}
/**
* @namespace OCA.LauruxContact.Affichercontact
*/
OCA.LauruxContact.Affichercontact = {
initialize: function() {
$('.saveInCSV').on('click', _.bind(this._clickSaveInCSV, this));
},
_clickSaveInCSV : function(e)
{
OC.msg.startSaving('#error_label_csv');
var request = $.ajax({
url: OC.generateUrl('/apps/lauruxcontact/showcontact/saveInCSV'),
type: 'POST',
data: {
utilisateur: e.target.id,
}
});
request.done(function ()
{
OC.msg.finishedSuccess('#error_label_csv', 'Sauvegardé !');
});
request.fail(function () {
OC.msg.finishedError('#error_label_csv', 'Erreur !');
});
}
}
})();
$(document).ready(function(){
OCA.LauruxContact.Affichercontact.initialize();
});
The route
['name' => 'showcontact#saveInCSV', 'url' => '/showcontact/saveInCSV', 'verb' => 'POST'],
Tests
First test
After severals test, I have the good headers but the button don’t cant download the file at client !
CSVResponse
<?php
namespace OCA\LauruxContact\Response;
use OCP\AppFramework\Http\Response;
class CSVResponse extends Response
{
private $csv;
public function __construct(array $csv)
{
$filename = "contact_export_" . date("Y-m-d") . ".csv";
$now = gmdate("D, d M Y H:i:s");
$this->addHeader('Content-Type', 'application/octet-stream');
$this->addHeader("Cache-Control", "max-age=0, no-cache, must-revalidate, proxy-revalidate");
$this->addHeader("Last-Modified", $now." GMT");
$this->addHeader("Content-Disposition", "attachment;filename=".$filename);
$this->addHeader("Content-Transfer-Encoding","binary");
$this->csv = $csv;
}
public function render()
{
$res = "";
foreach($this->csv as $row)
{
$res = $res . implode(",", $row) . ",\n";
}
return $res;
}
}
saveInCSV()
public function saveInCSV($utilisateur)
{
$contact = $this->contactsManager->search($utilisateur, ['UID'], ['types' => true])[0];
$filename = "contact_export_" . date("Y-m-d") . ".csv";
$data = array(
array("prenom", explode(" ", $contact['FN'])[1])
);
return new CSVResponse($data);
}
The header
Cache-Control: max-age=0, no-cache, must-revalidate, proxy-revalidate
Connection: Keep-Alive
Content-Disposition: attachment;filename=contact_export_2020-06-15.csv
Content-Length: 14
Content-Security-Policy: default-src 'none';base-uri 'none';manifest-src 'self'
Content-Transfer-Encoding: binary
Content-Type: application/octet-stream
Date: Mon, 15 Jun 2020 15:01:36 GMT
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Feature-Policy: autoplay 'none';camera 'none';fullscreen 'none';geolocation 'none';microphone 'none';payment 'none'
Keep-Alive: timeout=5, max=72
Last-Modified: Mon, 15 Jun 2020 15:01:37 GMT
Pragma: no-cache
Referrer-Policy: no-referrer
Server: Apache/2.4.41 (Ubuntu)
Strict-Transport-Security: max-age=15768000; includeSubDomains; preload
X-Content-Type-Options: nosniff
X-Download-Options: noopen
X-Frame-Options: SAMEORIGIN
X-Permitted-Cross-Domain-Policies: none
X-Robots-Tag: none
X-XSS-Protection: 1; mode=block
Second test
With my research I had found a new class which is OCP\AppFramework\Http\DownloadResponse ! But it’s doesn’t work too, the header is correct but he don’t want download file.
CSVDownloadResponse
<?php
namespace OCA\LauruxContact\Response;
use OCP\AppFramework\Http\DownloadResponse;
class CSVDownloadResponse extends DownloadResponse
{
private $content;
public function __construct(string $content, string $filename, string $contentType) {
parent::__construct($filename, $contentType);
$this->content = $content;
}
public function render(): string {
return $this->content;
}
}
saveInCSV()
public function saveInCSV($id)
{
$contact = $this->contactsManager->search($id, ['UID'], ['types' => true])[0];
$filename = "contact_export_" . date("Y-m-d") . ".csv";
$csv = array(
array("prenom", explode(" ", $contact['FN'])[1])
);
$res = "";
foreach($csv as $row)
{
$res = $res . implode(",", $row) . ",\n";
}
return new CSVDownloadResponse($res, $filename, 'application/octet-stream');
}
showContact.js
_clickRetour : function()
{
document.location.href=OC.generateUrl('/apps/lauruxcontact/');
},
_clickSaveInCSV : function(e)
{
OC.msg.startSaving('#error_label_csv');
var request = $.ajax({
url: OC.generateUrl('/apps/lauruxcontact/showcontact/saveInCSV/'+e.target.id),
type: 'GET'
});
request.done(function ()
{
OC.msg.finishedSuccess('#error_label_csv', 'Sauvegardé !');
});
request.fail(function () {
OC.msg.finishedError('#error_label_csv', 'Erreur !');
});
}
Routes
['name' => 'showcontact#saveInCSV', 'url' => '/showcontact/saveInCSV/{id}', 'verb' => 'GET'],
Header
Cache-Control: no-cache, no-store, must-revalidate
Connection: Keep-Alive
Content-Disposition: attachment; filename="contact_export_2020-06-16.csv"
Content-Length: 14
Content-Security-Policy: default-src 'none';base-uri 'none';manifest-src 'self'
Content-Type: application/octet-stream
Date: Tue, 16 Jun 2020 09:02:02 GMT
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Feature-Policy: autoplay 'none';camera 'none';fullscreen 'none';geolocation 'none';microphone 'none';payment 'none'
Keep-Alive: timeout=5, max=61
Pragma: no-cache
Referrer-Policy: no-referrer
Server: Apache/2.4.41 (Ubuntu)
Strict-Transport-Security: max-age=15768000; includeSubDomains; preload
X-Content-Type-Options: nosniff
X-Download-Options: noopen
X-Frame-Options: SAMEORIGIN
X-Permitted-Cross-Domain-Policies: none
X-Robots-Tag: none
X-XSS-Protection: 1; mode=block
Do you have a solution ?