add import/Export contacts system

This commit is contained in:
osaajani 2019-11-30 05:31:20 +01:00
parent 6503eb62ec
commit cfde77a0c1
10 changed files with 509 additions and 12 deletions

View File

@ -23,6 +23,11 @@
font-style: italic;
}
.float-right
{
float: right;
}
/** POPUPS ALERT **/
.popup-alerts-container
{
@ -276,6 +281,17 @@ footer
/* CONTACT */
#import-modal input:valid + label .valid-icon
{
color: #4cae4c;
display: inline-block !important;
}
#import-modal input:invalid + label .invalid-icon
{
display: inline-block !important;
}
.contact-datas-container input
{
display: inline-block;

View File

@ -109,4 +109,229 @@ namespace controllers\internals;
return $this->get_model()->update_for_user($id_user, $id, $contact);
}
/**
* Import a list of contacts as csv
* @param resource $file_handler : File handler to import contacts from
* @param int $id_user : User id
* @return mixed : False on error, number of inserted contacts else
*/
public function import_csv (int $id_user, $file_handler)
{
if (!is_resource($file_handler))
{
return false;
}
$nb_insert = 0;
$head = null;
while ($line = fgetcsv($file_handler))
{
if ($head === null)
{
$head = $line;
continue;
}
$line = array_combine($head, $line);
if ($line === false)
{
continue;
}
if (!isset($line['name'], $line['number']))
{
continue;
}
$datas = [];
foreach ($line as $key => $value)
{
if ($key == 'name' || $key == 'number')
{
continue;
}
if ($value === '')
{
continue;
}
$key = mb_ereg_replace('[\W]', '', $key);
$datas[$key] = $value;
}
$datas = json_encode($datas);
$success = $this->create($id_user, $line['number'], $line['name'], $datas);
if ($success)
{
$nb_insert ++;
}
}
return $nb_insert;
}
/**
* Import a list of contacts as json
* @param resource $file_handler : File handler to import contacts from
* @param int $id_user : User id
* @return mixed : False on error, number of inserted contacts else
*/
public function import_json (int $id_user, $file_handler)
{
if (!is_resource($file_handler))
{
return false;
}
$file_content = '';
while ($line = fgets($file_handler))
{
$file_content .= $line;
}
try
{
$contacts = json_decode($file_content, true);
if (!is_array($contacts))
{
return false;
}
$nb_insert = 0;
foreach ($contacts as $contact)
{
if (!is_array($contact))
{
continue;
}
if (!isset($contact['name'], $contact['number']))
{
continue;
}
$datas = $contact['datas'] ?? [];
$datas = json_encode($datas);
$success = $this->create($id_user, $contact['number'], $contact['name'], $datas);
if ($success)
{
$nb_insert ++;
}
}
return $nb_insert;
}
catch (\Exception $e)
{
return false;
}
}
/**
* Export the contacts of a user as csv
* @param int $id_user : User id
* @return array : ['headers' => array of headers to return, 'content' => the generated file]
*/
public function export_csv (int $id_user) : array
{
$contacts = $this->get_model()->gets_for_user($id_user);
$columns = [
'name',
'number',
];
foreach ($contacts as $contact)
{
$datas = json_decode($contact['datas'], true);
foreach ($datas as $key => $value)
{
$columns[] = $key;
}
}
$columns = array_unique($columns);
$lines = [];
foreach ($contacts as $contact)
{
$datas = json_decode($contact['datas'], true);
$line = [];
foreach ($columns as $column)
{
if (isset($contact[$column]))
{
$line[] = $contact[$column];
continue;
}
if (isset($datas[$column]))
{
$line[] = $datas[$column];
continue;
}
$line[] = null;
}
$lines[] = $line;
}
//Php only support csv formatting to file. To get it in string we need to create a tmp in memory file, write in it, and then read the file into a var
// output up to 5MB is kept in memory, if it becomes bigger it will automatically be written to a temporary file
$csv_tmp_file = fopen('php://temp/maxmemory:'. (5*1024*1024), 'r+');
fputcsv($csv_tmp_file, $columns);
foreach ($lines as $line)
{
fputcsv($csv_tmp_file, $line);
}
rewind($csv_tmp_file);
$csv_string = stream_get_contents($csv_tmp_file);
return [
'headers' => [
'Content-Disposition: attachment; filename=contacts.csv',
'Content-Type: text/csv',
'Content-Length: ' . strlen($csv_string),
],
'content' => $csv_string,
];
}
/**
* Export the contacts of a user as json
* @param int $id_user : User id
* @return array : ['headers' => array of headers to return, 'content' => the generated file]
*/
public function export_json (int $id_user) : array
{
$contacts = $this->get_model()->gets_for_user($id_user);
foreach ($contacts as &$contact)
{
unset($contact['id']);
unset($contact['id_user']);
$contact['datas'] = json_decode($contact['datas']);
}
$content = json_encode($contacts);
return [
'headers' => [
'Content-Disposition: attachment; filename=contacts.json',
'Content-Type: application/json',
'Content-Length: ' . strlen($content),
],
'content' => $content,
];
}
}

View File

@ -26,13 +26,24 @@ namespace controllers\internals;
*/
public static function parse_phone($number)
{
$number = preg_replace('#[^-0-9+]#', '', $number);
if (preg_match('#^(0|\+[1-9]{1,3}|\+1\-[0-9]{3})[1-9][0-9]{8,10}$#', $number))
try
{
return $number;
}
$phone_number_util = \libphonenumber\PhoneNumberUtil::getInstance();
$phone_number_o = $phone_number_util->parse($number, null);
return false;
$valid = $phone_number_util->isValidNumber($phone_number_o);
if (!$valid)
{
return false;
}
return $phone_number_util->format($phone_number_o, \libphonenumber\PhoneNumberFormat::E164);
}
catch(\Exception $e)
{
return false;
}
}
/**
@ -44,10 +55,17 @@ namespace controllers\internals;
*/
public static function phone_format($number)
{
$phone_number_util = \libphonenumber\PhoneNumberUtil::getInstance();
$phone_number_o = $phone_number_util->parse($number, null);
try
{
$phone_number_util = \libphonenumber\PhoneNumberUtil::getInstance();
$phone_number_o = $phone_number_util->parse($number, null);
return $phone_number_util->format($phone_number_o, \libphonenumber\PhoneNumberFormat::INTERNATIONAL);
return $phone_number_util->format($phone_number_o, \libphonenumber\PhoneNumberFormat::INTERNATIONAL);
}
catch(\Exception $e)
{
return $number;
}
}
/**
@ -205,4 +223,69 @@ namespace controllers\internals;
return mail($to, $settings['subject'], $content);
}
/**
* Allow to read an uploaded file
* @param array $file : The array extracted from $_FILES['file']
* @return array : ['success' => bool, 'content' => file handler | error message, 'error_code' => $file['error']]
*/
public static function read_uploaded_file(array $file)
{
$result = [
'success' => false,
'content' => 'Une erreur inconnue est survenue.',
'error_code' => $file['error'] ?? 99,
'mime_type' => false,
];
if ($file['error'] !== UPLOAD_ERR_OK)
{
switch ($file['error'])
{
case UPLOAD_ERR_INI_SIZE :
$result['content'] = 'Impossible de télécharger le fichier car il dépasse les ' . ini_get('upload_max_filesize') / (1000 * 1000) . ' Mégaoctets.';
break;
case UPLOAD_ERR_FORM_SIZE :
$result['content'] = 'Le fichier dépasse la limite de taille.';
break;
case UPLOAD_ERR_PARTIAL :
$result['content'] = 'L\'envoi du fichier a été interrompu.';
break;
case UPLOAD_ERR_NO_FILE :
$result['content'] = 'Aucun fichier n\'a été envoyé.';
break;
case UPLOAD_ERR_NO_TMP_DIR :
$result['content'] = 'Le serveur ne dispose pas de fichier temporaire permettant l\'envoi de fichiers.';
break;
case UPLOAD_ERR_CANT_WRITE :
$result['content'] = 'Impossible d\'envoyer le fichier car il n\'y a plus de place sur le serveur.';
break;
case UPLOAD_ERR_EXTENSION :
$result['content'] = 'Le serveur a interrompu l\'envoi du fichier.';
break;
}
return $result;
}
$tmp_filename = $file['tmp_name'] ?? false;
if (!$tmp_filename || !is_readable($tmp_filename))
{
return $result;
}
$result['mime_type'] = mime_content_type($tmp_filename) == 'text/plain' ? $file['type'] : mime_content_type($tmp_filename);
$file_handler = fopen($tmp_filename, 'r');
$result['success'] = true;
$result['content'] = $file_handler;
return $result;
}
}

View File

@ -243,6 +243,120 @@ namespace controllers\publics;
return $this->redirect(\descartes\Router::url('Contact', 'list'));
}
/**
* Allow to import a contacts list
* @param string $csrf : Csrf token
* @param $_FILES['contacts_list_file'] : A csv file of the contacts to import
*/
public function import (string $csrf)
{
if (!$this->verify_csrf($csrf))
{
\FlashMessage\FlashMessage::push('danger', 'Jeton CSRF invalid !');
return $this->redirect(\descartes\Router::url('Contact', 'list'));
}
$id_user = $_SESSION['user']['id'];
$upload_array = $_FILES['contacts_list_file'] ?? false;
if (!$upload_array)
{
\FlashMessage\FlashMessage::push('danger', 'Vous devez fournir un fichier de contacts à importer.');
return $this->redirect(\descartes\Router::url('Contact', 'list'));
}
$read_file = \controllers\internals\Tool::read_uploaded_file($upload_array);
if (!$read_file['success'])
{
\FlashMessage\FlashMessage::push('danger', $read_file['content']);
return $this->redirect(\descartes\Router::url('Contact', 'list'));
}
//Try to import file
$invalid_type = false;
switch ($read_file['mime_type'])
{
case 'text/csv' :
$result = $this->internal_contact->import_csv($id_user, $read_file['content']);
break;
case 'application/json' :
$result = $this->internal_contact->import_json($id_user, $read_file['content']);
break;
default :
$invalid_type = true;
}
if ($invalid_type)
{
\FlashMessage\FlashMessage::push('danger', 'Le type de fichier n\'est pas valide.');
return $this->redirect(\descartes\Router::url('Contact', 'list'));
}
if ($result === false)
{
\FlashMessage\FlashMessage::push('danger', 'Le fichier contient des erreurs. Impossible d\'importer les contacts.');
return $this->redirect(\descartes\Router::url('Contact', 'list'));
}
$msg = $result . ' nouveau contact a été inséré.';
if ($result > 1)
{
$msg = $result . ' nouveaux contacts ont été insérés.';
}
\FlashMessage\FlashMessage::push('success', $msg);
return $this->redirect(\descartes\Router::url('Contact', 'list'));
}
/**
* Allow to export a contacts list
* @param $format : Format to export contacts to
*/
public function export (string $format)
{
$id_user = $_SESSION['user']['id'];
//Try to export contacts
$invalid_type = false;
switch ($format)
{
case 'csv' :
$result = $this->internal_contact->export_csv($id_user);
break;
case 'json' :
$result = $this->internal_contact->export_json($id_user);
break;
default :
$invalid_type = true;
}
if ($invalid_type)
{
\FlashMessage\FlashMessage::push('danger', 'Le format demandé n\'est pas supporté.');
return $this->redirect(\descartes\Router::url('Contact', 'list'));
}
if ($result === false)
{
\FlashMessage\FlashMessage::push('danger', 'Nous ne sommes par parveu à exporté les contacts.');
return $this->redirect(\descartes\Router::url('Contact', 'list'));
}
$result['headers'] = $result['headers'] ?? [];
foreach ($result['headers'] as $header)
{
header($header);
}
echo $result['content'];
}
/**
* Cette fonction retourne la liste des contacts sous forme JSON.
*/

View File

@ -44,6 +44,8 @@
'delete' => '/contact/delete/{csrf}/',
'edit' => '/contact/edit/',
'update' => '/contact/update/{csrf}/',
'import' => '/contact/import/{csrf}/',
'export' => '/contact/export/{format}/',
'json_list' => '/contacts.json/',
],

View File

@ -14,6 +14,8 @@
<div class="col-lg-12">
<h1 class="page-header">
Dashboard <small>Contacts</small>
<a class="btn btn-info float-right" id="btn-export" href="#"><span class="fa fa-upload"></span> Exporter la liste des contacts</a>
<a class="btn btn-info float-right" id="btn-import" href="#" style="margin-right: 10px;"><span class="fa fa-download"></span> Importer une liste de contacts</a>
</h1>
<ol class="breadcrumb">
<li>
@ -82,5 +84,60 @@
</div>
</div>
</div>
<div class="modal fade" tabindex="-1" id="import-modal">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<form action="<?php $this->s(\descartes\Router::url('Contact', 'import', ['csrf' => $_SESSION['csrf']])); ?>" method="POST" enctype="multipart/form-data">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">Importer une liste de contacts</h4>
</div>
<div class="modal-body text-center">
<p>Vous pouvez importer une liste aux formats suivants : CSV ou JSON.</p>
<input id="contacts_list_file" type="file" name="contacts_list_file" class="hidden" required="required">
<label class="btn btn-default" for="contacts_list_file"><span class="fa fa-file-text-o hidden invalid-icon"></span><span class="fa fa-check hidden valid-icon"></span> Choisir le fichier</label>
</div>
<div class="modal-footer">
<a type="button" class="btn btn-danger" data-dismiss="modal">Annuler</a>
<input type="submit" class="btn btn-success" value="Valider" />
</div>
</form>
</div>
</div>
</div>
<div class="modal fade" tabindex="-1" id="export-modal">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<form action="<?php $this->s(\descartes\Router::url('Contact', 'import', ['csrf' => $_SESSION['csrf']])); ?>" method="POST" enctype="multipart/form-data">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">Exporter la liste des contacts</h4>
</div>
<div class="modal-body text-center">
<p>Vous pouvez exporter la liste aux formats suivants.</p>
<a target="_blank" href="<?php $this->s(\descartes\Router::url('Contact', 'export', ['format' => 'csv'])); ?>" class="btn btn-default">CSV</a>
<a target="_blank" href="<?php $this->s(\descartes\Router::url('Contact', 'export', ['format' => 'json'])); ?>" class="btn btn-default">JSON</a>
</div>
<div class="modal-footer">
<a type="button" class="btn btn-danger" data-dismiss="modal">Annuler</a>
</div>
</form>
</div>
</div>
</div>
<script>
jQuery(document).ready(function()
{
jQuery('body').on('click', '#btn-import', function ()
{
jQuery('#import-modal').modal({'keyboard': true});
});
jQuery('body').on('click', '#btn-export', function ()
{
jQuery('#export-modal').modal({'keyboard': true});
});
});
</script>
<?php
$this->render('incs/footer');

View File

@ -68,7 +68,7 @@
<?php if ($_SESSION['user']['admin']) { ?>
<div class="text-right col-xs-12 no-padding">
<strong>Action pour la séléction :</strong>
<button class="btn btn-default" type="submit" formaction="<?php echo \descartes\Router::url('Event', 'delete', ['csrf' => $_SESSION['csrf']]); ?>"><span class="fa fa-trash-o"></span> Supprimer</button>
<button class="btn btn-default btn-confirm" type="submit" formaction="<?php echo \descartes\Router::url('Event', 'delete', ['csrf' => $_SESSION['csrf']]); ?>"><span class="fa fa-trash-o"></span> Supprimer</button>
</div>
<?php } ?>
<ul class="pager">

View File

@ -70,7 +70,7 @@
<?php if ($_SESSION['user']['admin']) { ?>
<div class="text-right col-xs-12 no-padding">
<strong>Action pour la séléction :</strong>
<button class="btn btn-default" type="submit" formaction="<?php echo \descartes\Router::url('Received', 'delete', ['csrf' => $_SESSION['csrf']]); ?>"><span class="fa fa-trash-o"></span> Supprimer</button>
<button class="btn btn-default btn-confirm" type="submit" formaction="<?php echo \descartes\Router::url('Received', 'delete', ['csrf' => $_SESSION['csrf']]); ?>"><span class="fa fa-trash-o"></span> Supprimer</button>
</div>
<?php } ?>
<ul class="pager">

View File

@ -82,7 +82,7 @@
<?php if ($_SESSION['user']['admin']) { ?>
<div class="text-right col-xs-12 no-padding">
<strong>Action pour la séléction :</strong>
<button class="btn btn-default" type="submit" formaction="<?php echo \descartes\Router::url('Sended', 'delete', ['csrf' => $_SESSION['csrf']]); ?>"><span class="fa fa-trash-o"></span> Supprimer</button>
<button class="btn btn-default btn-confirm" type="submit" formaction="<?php echo \descartes\Router::url('Sended', 'delete', ['csrf' => $_SESSION['csrf']]); ?>"><span class="fa fa-trash-o"></span> Supprimer</button>
</div>
<?php } ?>
<ul class="pager">

View File

@ -64,7 +64,7 @@
<?php if ($_SESSION['user']['admin']) { ?>
<div class="text-right col-xs-12 no-padding">
<strong>Action pour la séléction :</strong>
<button class="btn btn-default" type="submit" formaction="<?php echo \descartes\Router::url('SmsStop', 'delete', ['csrf' => $_SESSION['csrf']]); ?>"><span class="fa fa-trash-o"></span> Supprimer</button>
<button class="btn btn-default btn-confirm" type="submit" formaction="<?php echo \descartes\Router::url('SmsStop', 'delete', ['csrf' => $_SESSION['csrf']]); ?>"><span class="fa fa-trash-o"></span> Supprimer</button>
</div>
<?php } ?>
<ul class="pager">