3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
6 * Galette CSV controller
10 * Copyright © 2019-2020 The Galette Team
12 * This file is part of Galette (http://galette.tuxfamily.org).
14 * Galette is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation, either version 3 of the License, or
17 * (at your option) any later version.
19 * Galette is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
24 * You should have received a copy of the GNU General Public License
25 * along with Galette. If not, see <http://www.gnu.org/licenses/>.
27 * @category Controllers
30 * @author Johan Cwiklinski <johan@x-tnd.be>
31 * @copyright 2019-2020 The Galette Team
32 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
34 * @link http://galette.tuxfamily.org
35 * @since Available since 0.9.4dev - 2019-12-06
38 namespace Galette\Controllers
;
40 use Slim\Http\Request
;
41 use Slim\Http\Response
;
42 use Galette\Entity\ImportModel
;
43 use Galette\Filters\MembersList
;
46 use Galette\IO\CsvOut
;
47 use Galette\IO\MembersCsv
;
48 use Galette\Repository\DynamicFieldsSet
;
52 * Galette CSV controller
54 * @category Controllers
57 * @author Johan Cwiklinski <johan@x-tnd.be>
58 * @copyright 2019-2020 The Galette Team
59 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
60 * @link http://galette.tuxfamily.org
61 * @since Available since 0.9.4dev - 2019-12-06
64 class CsvController
extends AbstractController
69 * @param Response $response PSR Response
70 * @param string $filepath File path on disk
71 * @param string $filename File name for output
75 protected function sendResponse(Response
$response, $filepath, $filename): Response
77 if (file_exists($filepath)) {
78 $response = $response->withHeader('Content-Description', 'File Transfer')
79 ->withHeader('Content-Type', 'text/csv')
80 ->withHeader('Content-Disposition', 'attachment;filename="' . $filename . '"')
81 ->withHeader('Pragma', 'no-cache')
82 ->withHeader('Content-Transfer-Encoding', 'binary')
83 ->withHeader('Expires', '0')
84 ->withHeader('Cache-Control', 'must-revalidate')
85 ->withHeader('Pragma', 'public');
87 $stream = fopen('php://memory', 'r+');
88 fwrite($stream, file_get_contents($filepath));
91 return $response->withBody(new \Slim\Http\
Stream($stream));
94 'A request has been made to get a CSV file named `' .
95 $filename . '` that does not exists (' . $filepath . ').',
98 $notFound = $this->notFoundHandler
;
99 return $notFound($request, $response);
106 * @param Request $request PSR Request
107 * @param Response $response PSR Response
111 public function export(Request
$request, Response
$response): Response
115 $tables_list = $this->zdb
->getTables();
116 $parameted = $csv->getParametedExports();
117 $existing = $csv->getExisting();
124 'page_title' => _T("CVS database Export"),
125 'tables_list' => $tables_list,
126 'written' => $this->flash
->getMessage('written_exports'),
127 'existing' => $existing,
128 'parameted' => $parameted
137 * @param Request $request PSR Request
138 * @param Response $response PSR Response
142 public function doExport(Request
$request, Response
$response): Response
144 $post = $request->getParsedBody();
148 if (isset($post['export_tables']) && $post['export_tables'] != '') {
149 foreach ($post['export_tables'] as $table) {
150 $select = $this->zdb
->sql
->select($table);
151 $results = $this->zdb
->execute($select);
153 if ($results->count() > 0) {
154 $filename = $table . '_full.csv';
155 $filepath = CsvOut
::DEFAULT_DIRECTORY
. $filename;
156 $fp = fopen($filepath, 'w');
160 Csv
::DEFAULT_SEPARATOR
,
172 $this->flash
->addMessage(
177 _T("Table %table is empty, and has not been exported.")
184 if (isset($post['export_parameted']) && $post['export_parameted'] != '') {
185 foreach ($post['export_parameted'] as $p) {
186 $res = $csv->runParametedExport($p);
187 $pn = $csv->getParamedtedExportName($p);
189 case Csv
::FILE_NOT_WRITABLE
:
190 $this->flash
->addMessage(
195 _T("Export file could not be write on disk for '%export'. Make sure web server can write in the exports directory.")
200 $this->flash
->addMessage(
205 _T("An error occurred running parameted export '%export'.")
210 $this->flash
->addMessage(
215 _T("An error occurred running parameted export '%export'. Please check the logs.")
220 //no error, file has been writted to disk
223 'file' => (string)$res
230 if (count($written)) {
231 foreach ($written as $ex) {
232 $path = $this->router
->pathFor('getCsv', ['type' => 'export', 'file' => $ex['file']]);
233 $this->flash
->addMessage(
235 '<a href="' . $path . '">' . $ex['name'] . ' (' . $ex['file'] . ')</a>'
242 ->withHeader('Location', $this->router
->pathFor('export'));
248 * @param Request $request PSR Request
249 * @param Response $response PSR Response
253 public function import(Request
$request, Response
$response): Response
255 $csv = new CsvIn($this->zdb
);
256 $existing = $csv->getExisting();
264 'page_title' => _T("CSV members import"),
265 'existing' => $existing,
267 'import_file' => $this->session
->import_file
276 * @param Request $request PSR Request
277 * @param Response $response PSR Response
281 public function doImports(Request
$request, Response
$response): Response
283 $csv = new CsvIn($this->zdb
);
284 $post = $request->getParsedBody();
285 $dryrun = isset($post['dryrun']);
287 //store selected file to dispaly again in UI
288 $this->session
->import_file
= $post['import_file'];
294 $post['import_file'],
295 $this->members_fields
,
296 $this->members_fields_cats
,
301 $this->flash
->addMessage(
303 $csv->getErrorMessage($res)
305 if (count($csv->getErrors()) > 0) {
306 foreach ($csv->getErrors() as $error) {
307 $this->flash
->addMessage(
314 $this->flash
->addMessage(
316 _T("An error occurred importing the file :(")
320 if ($this->session
->import_file
&& !$dryrun) {
321 $this->session
->import_file
= null;
323 $this->flash
->addMessage(
327 $post['import_file'],
328 _T("File '%filename%' has been successfully imported :)")
334 ->withHeader('Location', $this->router
->pathFor('import'));
338 * Get CSV file (imports or exports)
340 * @param Request $request PSR Request
341 * @param Response $response PSR Response
345 public function uploadImportFile(Request
$request, Response
$response): Response
347 $csv = new CsvIn($this->zdb
);
348 if (isset($_FILES['new_file'])) {
349 if ($_FILES['new_file']['error'] === UPLOAD_ERR_OK
) {
350 if ($_FILES['new_file']['tmp_name'] != '') {
351 if (is_uploaded_file($_FILES['new_file']['tmp_name'])) {
352 $res = $csv->store($_FILES['new_file']);
354 $this->flash
->addMessage(
356 $csv->getErrorMessage($res)
359 $this->flash
->addMessage(
361 _T("Your file has been successfully uploaded!")
366 } elseif ($_FILES['new_file']['error'] !== UPLOAD_ERR_NO_FILE
) {
368 $csv->getPhpErrorMessage($_FILES['new_file']['error']),
371 $this->flash
->addMessage(
373 $csv->getPhpErrorMessage(
374 $_FILES['new_file']['error']
377 } elseif (isset($_POST['upload'])) {
378 $this->flash
->addMessage(
380 _T("No files has been seleted for upload!")
384 $this->flash
->addMessage(
386 _T("No files has been uploaded!")
392 ->withHeader('Location', $this->router
->pathFor('import'));
396 * Get CSV file (imports or exports)
398 * @param Request $request PSR Request
399 * @param Response $response PSR Response
400 * @param array $args Request arguments
404 public function getFile(Request
$request, Response
$response, array $args = []): Response
406 $filename = $args['file'];
408 //Exports main contain user confidential data, they're accessible only for
409 //admins or staff members
410 if ($this->login
->isAdmin() ||
$this->login
->isStaff()) {
411 $filepath = $args['type'] === 'export' ?
412 CsvOut
::DEFAULT_DIRECTORY
: CsvIn
::DEFAULT_DIRECTORY
;
413 $filepath .= $filename;
414 return $this->sendResponse($response, $filepath, $filename);
417 'A non authorized person asked to retrieve ' . $args['type'] . ' file named `' .
418 $filename . '`. Access has not been granted.',
421 $error = $this->errorHandler
;
424 $response->withStatus(403)
430 * Remove CSV file confirmation (imports or exports)
432 * @param Request $request PSR Request
433 * @param Response $response PSR Response
434 * @param array $args Request arguments
438 public function confirmRemoveFile(Request
$request, Response
$response, array $args = []): Response
442 'redirect_uri' => $this->router
->pathFor($args['type'])
448 'confirm_removal.tpl',
450 'mode' => $request->isXhr() ?
'ajax' : '',
451 'page_title' => sprintf(
452 _T('Remove %1$s file %2$s'),
456 'form_url' => $this->router
->pathFor(
459 'type' => $args['type'],
460 'file' => $args['file']
463 'cancel_uri' => $data['redirect_uri'],
471 * Remove CSV file (imports or exports)
473 * @param Request $request PSR Request
474 * @param Response $response PSR Response
475 * @param array $args Request arguments
479 public function removeFile(Request
$request, Response
$response, array $args = []): Response
481 $post = $request->getParsedBody();
482 $ajax = isset($post['ajax']) && $post['ajax'] === 'true';
485 $uri = isset($post['redirect_uri']) ?
486 $post['redirect_uri'] : $this->router
->pathFor('slash');
488 if (!isset($post['confirm'])) {
489 $this->flash
->addMessage(
491 _T("Removal has not been confirmed!")
494 $csv = $args['type'] === 'export' ?
495 new CsvOut() : new CsvIn($this->zdb
);
496 $res = $csv->remove($args['file']);
499 $this->flash
->addMessage(
504 _T("'%export' file has been removed from disk.")
509 $this->flash
->addMessage(
514 _T("Cannot remove '%export' from disk :/")
523 ->withHeader('Location', $uri);
525 return $response->withJson(
527 'success' => $success
536 * @param Request $request PSR Request
537 * @param Response $response PSR Response
538 * @param array $args Request arguments
542 public function importModel(Request
$request, Response
$response, array $args = []): Response
544 $model = new ImportModel();
547 if (isset($request->getQueryParams()['remove'])) {
548 $model->remove($this->zdb
);
552 $csv = new CsvIn($this->zdb
);
555 * - set fields that should not be part of import
557 $fields = $model->getFields();
558 $defaults = $csv->getDefaultFields();
559 $defaults_loaded = false;
561 if ($fields === null) {
563 $defaults_loaded = true;
566 $import_fields = $this->members_form_fields
;
568 $dynamic_import_fields = [];
569 $fieldset = new DynamicFieldsSet($this->zdb
, $this->login
);
570 $dfields = $fieldset->getList('adh');
571 foreach ($dfields as $field) {
572 if ($field->hasData() && !$field instanceof \Galette\DynamicFields\File
) {
573 $dynamic_import_fields['dynfield_' . $field->getId()] = [
574 'label' => __($field->getname())
578 //we do not want to import id_adh. Never.
579 unset($import_fields['id_adh']);
580 $import_fields +
= $dynamic_import_fields;
587 'page_title' => _T("CSV import model"),
590 'defaults' => $defaults,
591 'members_fields' => $import_fields,
592 'defaults_loaded' => $defaults_loaded
599 * Get CSV import model file
601 * @param Request $request PSR Request
602 * @param Response $response PSR Response
603 * @param array $args Request arguments
607 public function getImportModel(Request
$request, Response
$response, array $args = []): Response
609 $model = new ImportModel();
612 $csv = new CsvIn($this->zdb
);
614 $fields = $model->getFields();
615 $defaults = $csv->getDefaultFields();
616 $defaults_loaded = false;
618 if ($fields === null) {
620 $defaults_loaded = true;
623 $ocsv = new CsvOut();
624 $res = $ocsv->export(
626 Csv
::DEFAULT_SEPARATOR
,
630 $filename = _T("galette_import_model.csv");
632 $response = $response->withHeader('Content-Description', 'File Transfer')
633 ->withHeader('Content-Type', 'text/csv')
634 ->withHeader('Content-Disposition', 'attachment;filename="' . $filename . '"')
635 ->withHeader('Pragma', 'no-cache')
636 ->withHeader('Content-Transfer-Encoding', 'binary')
637 ->withHeader('Expires', '0')
638 ->withHeader('Cache-Control', 'must-revalidate')
639 ->withHeader('Pragma', 'public');
641 $stream = fopen('php://memory', 'r+');
642 fwrite($stream, $res);
645 return $response->withBody(new \Slim\Http\
Stream($stream));
651 * @param Request $request PSR Request
652 * @param Response $response PSR Response
653 * @param array $args Request arguments
657 public function storeModel(Request
$request, Response
$response, array $args = []): Response
659 $model = new ImportModel();
662 $model->setFields($request->getParsedBody()['fields']);
663 $res = $model->store($this->zdb
);
665 $this->flash
->addMessage(
667 _T("Import model has been successfully stored :)")
670 $this->flash
->addMessage(
672 _T("Import model has not been stored :(")
678 ->withHeader('Location', $this->router
->pathFor('importModel'));
682 * Members CSV exports
684 * @param Request $request PSR Request
685 * @param Response $response PSR Response
686 * @param array $args Request arguments
690 public function membersExport(Request
$request, Response
$response, array $args = []): Response
692 $post = $request->getParsedBody();
693 $get = $request->getQueryParams();
695 $session_var = $post['session_var'] ??
$get['session_var'] ??
'filter_members';
697 if (isset($this->session
->$session_var)) {
698 $filters = $this->session
->$session_var;
700 $filters = new MembersList();
703 $csv = new MembersCsv(
706 $this->members_fields
,
709 $csv->exportMembers($filters);
711 $filepath = $csv->getPath();
712 $filename = $csv->getFileName();
714 return $this->sendResponse($response, $filepath, $filename);